From ee71a93a539b8fe9584ab1517ad53c52570656a7 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 13 Jul 2018 12:42:44 -0400 Subject: [PATCH 01/52] [DS-3953] Expose list of metadata namespace files as configuration. --- .../rdbms/DatabaseRegistryUpdater.java | 32 +++++++++++++------ dspace/config/dspace.cfg | 9 ++++++ 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseRegistryUpdater.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseRegistryUpdater.java index 209dec554b..44f938b084 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseRegistryUpdater.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseRegistryUpdater.java @@ -8,10 +8,17 @@ package org.dspace.storage.rdbms; import java.io.File; +import java.io.IOException; import java.sql.Connection; +import java.sql.SQLException; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; import org.dspace.administer.MetadataImporter; +import org.dspace.administer.RegistryImportException; import org.dspace.administer.RegistryLoader; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.NonUniqueMetadataException; import org.dspace.core.Context; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; @@ -21,6 +28,7 @@ import org.flywaydb.core.api.MigrationInfo; import org.flywaydb.core.api.callback.FlywayCallback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.xml.sax.SAXException; /** * This is a FlywayCallback class which automatically updates the @@ -49,7 +57,7 @@ public class DatabaseRegistryUpdater implements FlywayCallback { private static final Logger log = LoggerFactory.getLogger(DatabaseRegistryUpdater.class); /** - * Method to actually update our registries from latest configs + * Method to actually update our registries from latest configuration files. */ private void updateRegistries() { ConfigurationService config = DSpaceServicesFactory.getInstance().getConfigurationService(); @@ -63,28 +71,32 @@ public class DatabaseRegistryUpdater implements FlywayCallback { + "registries" + File.separator; // Load updates to Bitstream format registry (if any) - log.info("Updating Bitstream Format Registry based on " + base + "bitstream-formats.xml"); + log.info("Updating Bitstream Format Registry based on {}bitstream-formats.xml", base); RegistryLoader.loadBitstreamFormats(context, base + "bitstream-formats.xml"); // Load updates to Metadata schema registries (if any) - log.info("Updating Metadata Registries based on metadata type configs in " + base); - MetadataImporter.loadRegistry(base + "dublin-core-types.xml", true); - MetadataImporter.loadRegistry(base + "dcterms-types.xml", true); - MetadataImporter.loadRegistry(base + "local-types.xml", true); - MetadataImporter.loadRegistry(base + "eperson-types.xml", true); - MetadataImporter.loadRegistry(base + "sword-metadata.xml", true); + log.info("Updating Metadata Registries based on metadata type configs in {}", base); + for (String namespaceFile: config.getArrayProperty("registry.metadata.load")) { + log.info("Reading {}", namespaceFile); + MetadataImporter.loadRegistry(base + namespaceFile, true); + } // Check if XML Workflow is enabled in workflow.cfg if (WorkflowServiceFactory.getInstance().getWorkflowService() instanceof XmlWorkflowService) { // If so, load in the workflow metadata types as well - MetadataImporter.loadRegistry(base + "workflow-types.xml", true); + String workflowTypes = "workflow-types.xml"; + log.info("Reading {}", workflowTypes); + MetadataImporter.loadRegistry(base + workflowTypes, true); } context.restoreAuthSystemState(); // Commit changes and close context context.complete(); log.info("All Bitstream Format Regitry and Metadata Registry updates were completed."); - } catch (Exception e) { + } catch (IOException | SQLException | ParserConfigurationException + | TransformerException | RegistryImportException + | AuthorizeException | NonUniqueMetadataException + | SAXException e) { log.error("Error attempting to update Bitstream Format and/or Metadata Registries", e); throw new RuntimeException("Error attempting to update Bitstream Format and/or Metadata Registries", e); } finally { diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 908970092d..d1529d9e0c 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -751,6 +751,15 @@ org.dspace.app.batchitemimport.work.dir = ${dspace.dir}/imports # XSL file configured by: crosswalk.dissemination.DataCite.stylesheet file.) #identifier.doi.ezid.publisher = a publisher +##### Registry Loader ##### +# Metadata namespaces. +registry.metadata.load = dublin-core-types.xml +registry.metadata.load = dcterms-types.xml +registry.metadata.load = local-types.xml +registry.metadata.load = eperson-types.xml +registry.metadata.load = sword-metadata.xml + + #---------------------------------------------------------------# #--------------JSPUI & XMLUI CONFIGURATIONS---------------------# From 8db86e12f85d73533ea686db0dec3abf45785c40 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 2 Oct 2018 14:20:29 -0400 Subject: [PATCH 02/52] [DS-4032] Replace 'Boolean' with 'boolean' to avoid NPE. --- .../src/main/java/org/dspace/app/requestitem/RequestItem.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 d96cbbb5a4..8a77a591b7 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 @@ -78,7 +78,7 @@ public class RequestItem implements ReloadableEntity { private Date request_date = null; @Column(name = "accept_request") - private Boolean accept_request = null; + private boolean accept_request; /** * Protected constructor, create object using: @@ -88,6 +88,7 @@ public class RequestItem implements ReloadableEntity { protected RequestItem() { } + @Override public Integer getID() { return requestitem_id; } From b72c4218f43125fb324d3d3b83bff4ee8c3a4a27 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 13 Jul 2018 12:42:44 -0400 Subject: [PATCH 03/52] [DS-3953] Expose list of metadata namespace files as configuration. --- .../rdbms/DatabaseRegistryUpdater.java | 39 +++++++++++-------- dspace/config/dspace.cfg | 9 +++++ 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseRegistryUpdater.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseRegistryUpdater.java index 0f4e92fdda..c9e126f8fa 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseRegistryUpdater.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseRegistryUpdater.java @@ -8,10 +8,17 @@ package org.dspace.storage.rdbms; import java.io.File; +import java.io.IOException; import java.sql.Connection; +import java.sql.SQLException; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; import org.dspace.administer.MetadataImporter; +import org.dspace.administer.RegistryImportException; import org.dspace.administer.RegistryLoader; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.NonUniqueMetadataException; import org.dspace.core.Context; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; @@ -21,6 +28,7 @@ import org.flywaydb.core.api.MigrationInfo; import org.flywaydb.core.api.callback.FlywayCallback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.xml.sax.SAXException; /** * This is a FlywayCallback class which automatically updates the @@ -49,7 +57,7 @@ public class DatabaseRegistryUpdater implements FlywayCallback { private static final Logger log = LoggerFactory.getLogger(DatabaseRegistryUpdater.class); /** - * Method to actually update our registries from latest configs + * Method to actually update our registries from latest configuration files. */ private void updateRegistries() { ConfigurationService config = DSpaceServicesFactory.getInstance().getConfigurationService(); @@ -63,35 +71,32 @@ public class DatabaseRegistryUpdater implements FlywayCallback { + "registries" + File.separator; // Load updates to Bitstream format registry (if any) - log.info("Updating Bitstream Format Registry based on " + base + "bitstream-formats.xml"); + log.info("Updating Bitstream Format Registry based on {}bitstream-formats.xml", base); RegistryLoader.loadBitstreamFormats(context, base + "bitstream-formats.xml"); // Load updates to Metadata schema registries (if any) - log.info("Updating Metadata Registries based on metadata type configs in " + base); - MetadataImporter.loadRegistry(base + "dublin-core-types.xml", true); - MetadataImporter.loadRegistry(base + "dcterms-types.xml", true); - MetadataImporter.loadRegistry(base + "local-types.xml", true); - MetadataImporter.loadRegistry(base + "relationship-formats.xml", true); - MetadataImporter.loadRegistry(base + "person-types.xml", true); - MetadataImporter.loadRegistry(base + "project-types.xml", true); - MetadataImporter.loadRegistry(base + "orgunit-types.xml", true); - MetadataImporter.loadRegistry(base + "journal-types.xml", true); - MetadataImporter.loadRegistry(base + "journalissue-types.xml", true); - MetadataImporter.loadRegistry(base + "journalvolume-types.xml", true); - MetadataImporter.loadRegistry(base + "eperson-types.xml", true); - MetadataImporter.loadRegistry(base + "sword-metadata.xml", true); + log.info("Updating Metadata Registries based on metadata type configs in {}", base); + for (String namespaceFile: config.getArrayProperty("registry.metadata.load")) { + log.info("Reading {}", namespaceFile); + MetadataImporter.loadRegistry(base + namespaceFile, true); + } // Check if XML Workflow is enabled in workflow.cfg if (WorkflowServiceFactory.getInstance().getWorkflowService() instanceof XmlWorkflowService) { // If so, load in the workflow metadata types as well - MetadataImporter.loadRegistry(base + "workflow-types.xml", true); + String workflowTypes = "workflow-types.xml"; + log.info("Reading {}", workflowTypes); + MetadataImporter.loadRegistry(base + workflowTypes, true); } context.restoreAuthSystemState(); // Commit changes and close context context.complete(); log.info("All Bitstream Format Regitry and Metadata Registry updates were completed."); - } catch (Exception e) { + } catch (IOException | SQLException | ParserConfigurationException + | TransformerException | RegistryImportException + | AuthorizeException | NonUniqueMetadataException + | SAXException e) { log.error("Error attempting to update Bitstream Format and/or Metadata Registries", e); throw new RuntimeException("Error attempting to update Bitstream Format and/or Metadata Registries", e); } finally { diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index a290d7f36e..2bbd021fc8 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -766,6 +766,15 @@ org.dspace.app.batchitemimport.work.dir = ${dspace.dir}/imports # XSL file configured by: crosswalk.dissemination.DataCite.stylesheet file.) #identifier.doi.ezid.publisher = a publisher +##### Registry Loader ##### +# Metadata namespaces. +registry.metadata.load = dublin-core-types.xml +registry.metadata.load = dcterms-types.xml +registry.metadata.load = local-types.xml +registry.metadata.load = eperson-types.xml +registry.metadata.load = sword-metadata.xml + + #---------------------------------------------------------------# #--------------JSPUI & XMLUI CONFIGURATIONS---------------------# From 27d811f5e57241ce5c662c35f485bee2a5461432 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 9 May 2019 13:00:31 -0400 Subject: [PATCH 04/52] [DS-3953] Add new data for configurable entities. --- dspace/config/dspace.cfg | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 2bbd021fc8..7526910dcb 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -773,6 +773,13 @@ registry.metadata.load = dcterms-types.xml registry.metadata.load = local-types.xml registry.metadata.load = eperson-types.xml registry.metadata.load = sword-metadata.xml +registry.metadata.load = relationship-formats.xml +registry.metadata.load = person-types.xml +registry.metadata.load = project-types.xml +registry.metadata.load = orgunit-types.xml +registry.metadata.load = journal-types.xml +registry.metadata.load = journalissue-types.xml +registry.metadata.load = journalvolume-types.xml From f1073f0af062611240f856630cea1cd6c9753165 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 16 Nov 2018 22:41:55 +0000 Subject: [PATCH 05/52] Update RESTv7 to only use Spring Security on /api path --- .../security/WebSecurityConfiguration.java | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java index 259ece7d4a..16aef3ffcd 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java @@ -84,23 +84,24 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { //Logout configuration .logout() - //On logout, clear the "session" salt - .addLogoutHandler(customLogoutHandler) - //Configure the logout entry point - .logoutRequestMatcher(new AntPathRequestMatcher("/api/authn/logout")) - //When logout is successful, return OK (204) status - .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler(HttpStatus.NO_CONTENT)) - //Everyone can call this endpoint - .permitAll() + //On logout, clear the "session" salt + .addLogoutHandler(customLogoutHandler) + //Configure the logout entry point + .logoutRequestMatcher(new AntPathRequestMatcher("/api/authn/logout")) + //When logout is successful, return OK (204) status + .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler(HttpStatus.NO_CONTENT)) + //Everyone can call this endpoint + .permitAll() .and() //Configure the URL patterns with their authentication requirements - .authorizeRequests() - //Allow POST by anyone on the login endpoint - .antMatchers(HttpMethod.POST,"/api/authn/login").permitAll() - //TRACE, CONNECT, OPTIONS, HEAD - //Everyone can call GET on the status endpoint - .antMatchers(HttpMethod.GET, "/api/authn/status").permitAll() + //Enable Spring Security authorization on /api/ URLs only + .antMatcher("/api/**").authorizeRequests() + //Allow POST by anyone on the login endpoint + .antMatchers(HttpMethod.POST,"/api/authn/login").permitAll() + //TRACE, CONNECT, OPTIONS, HEAD + //Everyone can call GET on the status endpoint + .antMatchers(HttpMethod.GET, "/api/authn/status").permitAll() .and() //Add a filter before our login endpoints to do the authentication based on the data in the HTTP request From 5eb338d9b7246bc25297456beca252f838ceef23 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 16 Nov 2018 22:42:44 +0000 Subject: [PATCH 06/52] Update RESTv7 to ComponentScan for webapp configs under org.dspace.app.configuration --- .../java/org/dspace/app/rest/utils/ApplicationConfig.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java index 71651fa133..7e90a21c28 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java @@ -14,12 +14,15 @@ import org.springframework.data.web.config.EnableSpringDataWebSupport; /** * This class provide extra configuration for our Spring Boot Application - * + *

+ * NOTE: @ComponentScan on "org.dspace.app.configuration" provides a way for other modules or plugins + * to "inject" their own configurations / subpaths into our Spring Boot webapp. * @author Andrea Bollini (andrea.bollini at 4science.it) */ @Configuration @EnableSpringDataWebSupport -@ComponentScan( {"org.dspace.app.rest.converter", "org.dspace.app.rest.repository", "org.dspace.app.rest.utils"}) +@ComponentScan( {"org.dspace.app.rest.converter", "org.dspace.app.rest.repository", "org.dspace.app.rest.utils", + "org.dspace.app.configuration"}) public class ApplicationConfig { @Value("${dspace.dir}") private String dspaceHome; From af16e6e7804113a595982126974fd613b43e6546 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 16 Nov 2018 22:43:38 +0000 Subject: [PATCH 07/52] Remove SWORD overlay folders/files --- dspace/modules/sword/pom.xml | 129 ------------------ .../modules/sword/src/main/webapp/.gitignore | 0 2 files changed, 129 deletions(-) delete mode 100644 dspace/modules/sword/pom.xml delete mode 100644 dspace/modules/sword/src/main/webapp/.gitignore diff --git a/dspace/modules/sword/pom.xml b/dspace/modules/sword/pom.xml deleted file mode 100644 index a8498483ce..0000000000 --- a/dspace/modules/sword/pom.xml +++ /dev/null @@ -1,129 +0,0 @@ - - 4.0.0 - org.dspace.modules - sword - war - DSpace SWORD :: Local Customizations - - This project allows you to overlay your own local SWORD customizations - on top of the default SWORD web application provided with DSpace. - - - - - org.dspace - modules - 7.0-SNAPSHOT - .. - - - - - ${basedir}/../../.. - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - unpack - prepare-package - - unpack-dependencies - - - org.dspace.modules - additions - - ${project.build.directory}/additions - META-INF/** - - - - - - org.apache.maven.plugins - maven-war-plugin - - false - - true - - - - ${project.build.directory}/additions - WEB-INF/classes - - - - - - prepare-package - - - - - - - - - oracle-support - - - db.name - oracle - - - - - com.oracle - ojdbc6 - - - - - - - - org.dspace.modules - additions - - - xml-apis - xml-apis - - - - - org.dspace - dspace-sword - war - - - org.dspace - dspace-sword - jar - classes - - - javax.servlet - javax.servlet-api - provided - - - - diff --git a/dspace/modules/sword/src/main/webapp/.gitignore b/dspace/modules/sword/src/main/webapp/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 From 1b143aa5cdf9c9c6be7f858495d1197c8272e640 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 16 Nov 2018 22:45:08 +0000 Subject: [PATCH 08/52] Change SWORDv1 to a JAR dependency. Add as RESTv7 dependency --- dspace-spring-rest/pom.xml | 6 ++++++ dspace-sword/pom.xml | 9 ++++++++- dspace/pom.xml | 1 - pom.xml | 8 -------- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/dspace-spring-rest/pom.xml b/dspace-spring-rest/pom.xml index 0c904bfe60..390a1c27cd 100644 --- a/dspace-spring-rest/pom.xml +++ b/dspace-spring-rest/pom.xml @@ -256,6 +256,12 @@ org.dspace dspace-services + + + org.dspace + dspace-sword + + org.apache.commons commons-collections4 diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 63f149e01f..5a07a60e4a 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.dspace dspace-sword - war + jar DSpace SWORD DSpace SWORD Deposit Service Provider Web Application @@ -96,6 +96,13 @@ dspace-api-lang + + + org.springframework.boot + spring-boot-starter-web + 1.4.4.RELEASE + + jaxen jaxen diff --git a/dspace/pom.xml b/dspace/pom.xml index 5961bf9a1c..8b4427a508 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -227,7 +227,6 @@ org.dspace dspace-sword - classes compile diff --git a/pom.xml b/pom.xml index a06a94dfa6..c46616e5da 100644 --- a/pom.xml +++ b/pom.xml @@ -941,14 +941,6 @@ org.dspace dspace-sword 7.0-SNAPSHOT - jar - classes - - - org.dspace - dspace-sword - 7.0-SNAPSHOT - war org.dspace From 7f3877c7bc5c25f403f27791426ab39e36186369 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 16 Nov 2018 22:45:58 +0000 Subject: [PATCH 09/52] Create SWORDWebConfig to replace SWORD's web.xml. Fix compilation error in servlet --- .../app/configuration/SWORDWebConfig.java | 49 +++++++++++++++++++ .../sword/server/AtomDocumentServlet.java | 5 -- 2 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 dspace-sword/src/main/java/org/dspace/app/configuration/SWORDWebConfig.java diff --git a/dspace-sword/src/main/java/org/dspace/app/configuration/SWORDWebConfig.java b/dspace-sword/src/main/java/org/dspace/app/configuration/SWORDWebConfig.java new file mode 100644 index 0000000000..516cbec125 --- /dev/null +++ b/dspace-sword/src/main/java/org/dspace/app/configuration/SWORDWebConfig.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.configuration; + +import org.springframework.boot.web.servlet.ServletContextInitializer; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SWORDWebConfig { + + @Bean + public ServletContextInitializer swordv1ContextInitializer() { + return servletContext -> { + servletContext.setInitParameter("sword-server-class", "org.dspace.sword.DSpaceSWORDServer"); + servletContext.setInitParameter("authentication-method", "Basic"); + }; + } + + @Bean + public ServletRegistrationBean swordv1ServiceDocumentBean() { + ServletRegistrationBean bean = new ServletRegistrationBean( new org.purl.sword.server.ServiceDocumentServlet(), + "/sword/servicedocument/*"); + bean.setLoadOnStartup(1); + return bean; + } + + @Bean + public ServletRegistrationBean swordv1DepositBean() { + ServletRegistrationBean bean = new ServletRegistrationBean( new org.purl.sword.server.DepositServlet(), + "/sword/deposit/*"); + bean.setLoadOnStartup(1); + return bean; + } + + @Bean + public ServletRegistrationBean swordv1MediaLinkBean() { + ServletRegistrationBean bean = new ServletRegistrationBean( new org.purl.sword.server.AtomDocumentServlet(), + "/sword/media-link/*"); + bean.setLoadOnStartup(1); + return bean; + } +} diff --git a/dspace-sword/src/main/java/org/purl/sword/server/AtomDocumentServlet.java b/dspace-sword/src/main/java/org/purl/sword/server/AtomDocumentServlet.java index adf049c10a..0b2a675efd 100644 --- a/dspace-sword/src/main/java/org/purl/sword/server/AtomDocumentServlet.java +++ b/dspace-sword/src/main/java/org/purl/sword/server/AtomDocumentServlet.java @@ -27,11 +27,6 @@ import org.purl.sword.base.SWORDException; */ public class AtomDocumentServlet extends DepositServlet { - public AtomDocumentServlet() - throws ServletException { - super(); - } - /** * Process the get request. */ From fa61b737db54b4708f2142b0fb9dce2d8cd007a7 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 28 Nov 2018 17:11:08 +0000 Subject: [PATCH 10/52] Refactor Application startup to load DSpace configs early in boot process. Also fixes DS-3492 --- .../java/org/dspace/app/rest/Application.java | 13 ++--- .../app/rest/utils/ApplicationConfig.java | 19 ++++---- .../utils/DSpaceConfigurationInitializer.java | 48 +++++++++++++++++++ .../rest/utils/DSpaceKernelInitializer.java | 42 +++++++++------- .../src/main/resources/application.properties | 29 ++--------- dspace/config/modules/rest.cfg | 12 ++++- 6 files changed, 98 insertions(+), 65 deletions(-) create mode 100644 dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DSpaceConfigurationInitializer.java diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/Application.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/Application.java index 3e8328d478..0315860178 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/Application.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/Application.java @@ -14,6 +14,7 @@ import org.dspace.app.rest.filter.DSpaceRequestContextFilter; import org.dspace.app.rest.model.hateoas.DSpaceRelProvider; import org.dspace.app.rest.parameter.resolver.SearchFilterResolver; import org.dspace.app.rest.utils.ApplicationConfig; +import org.dspace.app.rest.utils.DSpaceConfigurationInitializer; import org.dspace.app.rest.utils.DSpaceKernelInitializer; import org.dspace.app.util.DSpaceContextListener; import org.dspace.utils.servlet.DSpaceWebappServletFilter; @@ -22,7 +23,6 @@ import org.slf4j.LoggerFactory; 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.ServletContextInitializer; import org.springframework.boot.web.support.SpringBootServletInitializer; import org.springframework.context.annotation.Bean; import org.springframework.core.annotation.Order; @@ -62,7 +62,6 @@ public class Application extends SpringBootServletInitializer { * This is necessary to allow us to build a deployable WAR, rather than * always relying on embedded Tomcat. *

- *

* See: http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-create-a-deployable-war-file * * @param application @@ -70,13 +69,10 @@ public class Application extends SpringBootServletInitializer { */ @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + // Pass this Application class, and our initializers for DSpace Kernel and Configuration + // NOTE: Kernel must be initialized before Configuration return application.sources(Application.class) - .initializers(new DSpaceKernelInitializer()); - } - - @Bean - public ServletContextInitializer contextInitializer() { - return servletContext -> servletContext.setInitParameter("dspace.dir", configuration.getDspaceHome()); + .initializers(new DSpaceKernelInitializer(), new DSpaceConfigurationInitializer()); } /** @@ -89,7 +85,6 @@ public class Application extends SpringBootServletInitializer { @Order(2) protected DSpaceContextListener dspaceContextListener() { // This listener initializes the DSpace Context object - // (and loads all DSpace configs) return new DSpaceContextListener(); } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java index 7e90a21c28..fd9059faa6 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java @@ -13,27 +13,24 @@ import org.springframework.context.annotation.Configuration; import org.springframework.data.web.config.EnableSpringDataWebSupport; /** - * This class provide extra configuration for our Spring Boot Application + * This class provides extra configuration for our Spring Boot Application *

- * NOTE: @ComponentScan on "org.dspace.app.configuration" provides a way for other modules or plugins - * to "inject" their own configurations / subpaths into our Spring Boot webapp. + * NOTE: @ComponentScan on "org.dspace.app.configuration" provides a way for other DSpace modules or plugins + * to "inject" their own Spring configurations / subpaths into our Spring Boot webapp. + * * @author Andrea Bollini (andrea.bollini at 4science.it) + * @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"}) public class ApplicationConfig { - @Value("${dspace.dir}") - private String dspaceHome; - - @Value("${cors.allowed-origins}") + // Allowed CORS origins. Defaults to * (everywhere) + // Can be overridden in DSpace configuration + @Value("${rest.cors.allowed-origins:*}") private String corsAllowedOrigins; - public String getDspaceHome() { - return dspaceHome; - } - public String[] getCorsAllowedOrigins() { if (corsAllowedOrigins != null) { return corsAllowedOrigins.split("\\s*,\\s*"); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DSpaceConfigurationInitializer.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DSpaceConfigurationInitializer.java new file mode 100644 index 0000000000..2e05ea3fb7 --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DSpaceConfigurationInitializer.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.rest.utils; + +import org.apache.commons.configuration2.Configuration; +import org.apache.commons.configuration2.spring.ConfigurationPropertySource; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * Utility class that will initialize the DSpace Configuration on Spring Boot startup. + *

+ * NOTE: MUST be loaded after DSpaceKernelInitializer, as it requires the kernel is already initialized. + *

+ * This initializer ensures that our DSpace Configuration is loaded into Spring's list of PropertySources + * very early in the Spring Boot startup process. That is important as it allows us to use DSpace configurations + * within @ConditionalOnProperty annotations on beans, as well as @Value annotations and XML bean definitions. + *

+ * Used by org.dspace.app.rest.Application + */ +public class DSpaceConfigurationInitializer implements ApplicationContextInitializer { + + private static final Logger log = LoggerFactory.getLogger(DSpaceConfigurationInitializer.class); + + @Override + public void initialize(final ConfigurableApplicationContext applicationContext) { + // Load DSpace Configuration service (requires kernel already initialized) + ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + Configuration configuration = configurationService.getConfiguration(); + + // Create an Apache Commons Configuration Property Source from our configuration + ConfigurationPropertySource apacheCommonsConfigPropertySource = + new ConfigurationPropertySource(configuration.getClass().getName(), configuration); + + // Append it to the Environment's list of PropertySources + applicationContext.getEnvironment().getPropertySources().addLast(apacheCommonsConfigPropertySource); + } +} + diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DSpaceKernelInitializer.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DSpaceKernelInitializer.java index 039cfa5af9..73a96259bf 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DSpaceKernelInitializer.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DSpaceKernelInitializer.java @@ -11,6 +11,7 @@ import java.io.File; import javax.naming.Context; import javax.naming.InitialContext; +import org.apache.commons.lang3.StringUtils; import org.dspace.kernel.DSpaceKernel; import org.dspace.kernel.DSpaceKernelManager; import org.dspace.servicemanager.DSpaceKernelImpl; @@ -22,6 +23,7 @@ import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.event.ContextClosedEvent; +import org.springframework.core.env.ConfigurableEnvironment; /** * Utility class that will initialize the DSpace Kernel on Spring Boot startup. @@ -35,16 +37,16 @@ public class DSpaceKernelInitializer implements ApplicationContextInitializer Date: Wed, 28 Nov 2018 18:19:15 +0000 Subject: [PATCH 11/52] Make SWORD module configurable via sword-server.cfg. Remove obsolete web.xml --- .../app/configuration/SWORDWebConfig.java | 39 +++++++- dspace-sword/src/main/webapp/WEB-INF/web.xml | 91 ------------------- dspace/config/modules/sword-server.cfg | 22 +++++ 3 files changed, 56 insertions(+), 96 deletions(-) delete mode 100644 dspace-sword/src/main/webapp/WEB-INF/web.xml diff --git a/dspace-sword/src/main/java/org/dspace/app/configuration/SWORDWebConfig.java b/dspace-sword/src/main/java/org/dspace/app/configuration/SWORDWebConfig.java index 516cbec125..4d1578385b 100644 --- a/dspace-sword/src/main/java/org/dspace/app/configuration/SWORDWebConfig.java +++ b/dspace-sword/src/main/java/org/dspace/app/configuration/SWORDWebConfig.java @@ -7,42 +7,71 @@ */ package org.dspace.app.configuration; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +/** + * SWORD webapp configuration. Replaces the old web.xml + *

+ * This @Configuration class is automatically discovered by Spring via a @ComponentScan. + *

+ * All SWORD web configurations (beans) can be enabled or disabled by setting "sword-server.enabled" + * to true or false, respectively (in your DSpace configuration). Default is "false". + *

+ * All @Value annotated configurations below can also be overridden in your DSpace configuration. + * + * @author Tim Donohue + */ @Configuration public class SWORDWebConfig { + // Path where SWORD should be deployed (when enabled). Defaults to "sword" + @Value("${sword-server.path:sword}") + private String swordPath; + + // SWORD Server class. Defaults to "org.dspace.sword.DSpaceSWORDServer" + @Value("${sword-server.class:org.dspace.sword.DSpaceSWORDServer}") + private String serverClass; + + // SWORD Authentication Method. Defaults to "Basic" + @Value("${sword-server.authentication-method:Basic}") + private String authenticationMethod; @Bean + @ConditionalOnProperty("sword-server.enabled") public ServletContextInitializer swordv1ContextInitializer() { return servletContext -> { - servletContext.setInitParameter("sword-server-class", "org.dspace.sword.DSpaceSWORDServer"); - servletContext.setInitParameter("authentication-method", "Basic"); + servletContext.setInitParameter("sword-server-class", serverClass); + servletContext.setInitParameter("authentication-method", authenticationMethod); }; } @Bean + @ConditionalOnProperty("sword-server.enabled") public ServletRegistrationBean swordv1ServiceDocumentBean() { ServletRegistrationBean bean = new ServletRegistrationBean( new org.purl.sword.server.ServiceDocumentServlet(), - "/sword/servicedocument/*"); + "/" + swordPath + "/servicedocument/*"); bean.setLoadOnStartup(1); return bean; } @Bean + @ConditionalOnProperty("sword-server.enabled") public ServletRegistrationBean swordv1DepositBean() { ServletRegistrationBean bean = new ServletRegistrationBean( new org.purl.sword.server.DepositServlet(), - "/sword/deposit/*"); + "/" + swordPath + "/deposit/*"); bean.setLoadOnStartup(1); return bean; } @Bean + @ConditionalOnProperty("sword-server.enabled") public ServletRegistrationBean swordv1MediaLinkBean() { ServletRegistrationBean bean = new ServletRegistrationBean( new org.purl.sword.server.AtomDocumentServlet(), - "/sword/media-link/*"); + "/" + swordPath + "/media-link/*"); bean.setLoadOnStartup(1); return bean; } diff --git a/dspace-sword/src/main/webapp/WEB-INF/web.xml b/dspace-sword/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 14b873ccd9..0000000000 --- a/dspace-sword/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,91 +0,0 @@ - - - - - DSpace SWORD Server - - - - The location of the DSpace home directory - dspace.dir - ${dspace.dir} - - - - The SWORDServer class name - sword-server-class - org.dspace.sword.DSpaceSWORDServer - - - - log4jConfiguration - ${dspace.dir}/config/log4j2.xml - - The location of the Log4J configuration - - - - - The type of authentication used : [Basic|None] - authentication-method - Basic - - - - - - org.dspace.app.util.DSpaceContextListener - - - - - org.dspace.servicemanager.servlet.DSpaceKernelServletContextListener - - - - - - servicedocument - org.purl.sword.server.ServiceDocumentServlet - - - - deposit - org.purl.sword.server.DepositServlet - - - - media-link - org.purl.sword.server.AtomDocumentServlet - - - - - - servicedocument - /servicedocument/* - - - - deposit - /deposit/* - - - - media-link - /media-link/* - - - diff --git a/dspace/config/modules/sword-server.cfg b/dspace/config/modules/sword-server.cfg index 6c6778d9c7..0da35ba881 100644 --- a/dspace/config/modules/sword-server.cfg +++ b/dspace/config/modules/sword-server.cfg @@ -6,6 +6,28 @@ # SWORD protocol) # #---------------------------------------------------------------# +# Whether or not to enable the SWORD v1 module +# When "true", the SWORD module is accessible on ${sword-server.path} +# When "false" or commented out, SWORD is disabled/inaccessible. +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +#sword-server.enabled = true + +# Path where SWORD v1 module is available (in the Spring REST webapp) +# Defaults to "sword", which means the SWORD mould would be available +# at ${dspace.restURL}/sword/ +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +#sword-server.path = sword + +# The SWORDServer class name (in charge of all SWORD activities) +# This Java class must implement 'org.purl.sword.server.SWORDServer' +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +#sword-server.class = org.dspace.sword.DSpaceSWORDServer + +# The Authentication Method SWORD should use. +# Valid values are "Basic" or "None". Default is "Basic" +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +#sword-server.authentication-method = Basic + # tell the SWORD METS implementation which package ingester to use # to install deposited content. This should refer to one of the # classes configured for: From 8e3d12a34dd982adabe9c73a01061c1fc917472b Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 30 Jan 2019 21:05:48 +0000 Subject: [PATCH 12/52] Add basic Integration Tests for SWORDv1 endpoint using new AbstractWebClientIntegrationTest --- dspace-spring-rest/pom.xml | 15 ++- .../AbstractControllerIntegrationTest.java | 16 +++- .../AbstractWebClientIntegrationTest.java | 93 +++++++++++++++++++ .../test/java/org/dspace/sword/Swordv1IT.java | 60 ++++++++++++ 4 files changed, 176 insertions(+), 8 deletions(-) create mode 100644 dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java create mode 100644 dspace-spring-rest/src/test/java/org/dspace/sword/Swordv1IT.java diff --git a/dspace-spring-rest/pom.xml b/dspace-spring-rest/pom.xml index 390a1c27cd..611113d355 100644 --- a/dspace-spring-rest/pom.xml +++ b/dspace-spring-rest/pom.xml @@ -29,8 +29,10 @@ org.dspace.app.rest.Application 1.4.4.RELEASE - 2.2.0 - + + 2.4.0 + + 6.2 @@ -273,7 +275,7 @@ com.nimbusds nimbus-jose-jwt - 6.2 + ${nimbus-jose-jwt.version} org.apache.solr @@ -295,13 +297,18 @@ org.json json + + + com.jayway.jsonpath + json-path + com.jayway.jsonpath json-path - test ${json-path.version} + test com.jayway.jsonpath diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java index 52a113d787..507ea02c0c 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java @@ -22,6 +22,7 @@ import org.apache.commons.io.Charsets; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.Application; import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.utils.DSpaceConfigurationInitializer; import org.dspace.app.rest.utils.DSpaceKernelInitializer; import org.junit.Assert; import org.junit.runner.RunWith; @@ -44,11 +45,18 @@ import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; import org.springframework.web.context.WebApplicationContext; /** - * Abstract controller integration test class that will take care of setting up the - * Spring Boot environment to run the integration test + * Abstract integration test class that will take care of setting up the Spring Boot environment to run + * integration tests against @Controller classes (Spring Controllers). + *

+ * This Abstract class uses Spring Boot's default mock environment testing scheme, which relies on MockMvc to "mock" + * a webserver and call Spring Controllers directly. This avoids the cost of starting a webserver. + *

+ * If you need to test a Servlet (or something not a Spring Controller), you will NOT be able to use this class. + * Instead, please use the AbstractWebClientIntegrationTest in this same package. * * @author Tom Desair * @author Tim Donohue + * @see org.dspace.app.rest.test.AbstractWebClientIntegrationTest */ // Run tests with JUnit 4 and Spring TestContext Framework @RunWith(SpringRunner.class) @@ -56,8 +64,8 @@ import org.springframework.web.context.WebApplicationContext; // 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 DSpaceKernelInitializer in Spring ApplicationContext (to initialize DSpace Kernel) -@ContextConfiguration(initializers = DSpaceKernelInitializer.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 AbstractControllerIntegrationTest extends AbstractIntegrationTestWithDatabase { diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java new file mode 100644 index 0000000000..29f81b4510 --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.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.test; + +import org.dspace.app.rest.Application; +import org.dspace.app.rest.utils.DSpaceConfigurationInitializer; +import org.dspace.app.rest.utils.DSpaceKernelInitializer; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.embedded.LocalServerPort; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Abstract web client integration test class that will initialize the Spring Boot test environment by starting up + * a full test webserver (on a random port). + *

+ * As running a test webserver is an expensive operation, this Abstract class is only necessary to perform + * Integration Tests on *Servlets*. If you are performing integration tests on a Spring Controller + * (@Controller annotation), you should use AbstractControllerIntegrationTest + *

+ * NOTE: The annotations on this class should be kept in sync with those on AbstractControllerIntegrationTest. + * The ONLY differences should be in the "webEnvironment" param passed to @SpringBootTest, and the removal + * of @WebAppConfiguration (which is only allowed in a mock environment) + * + * @author Tim Donohue + * @see org.dspace.app.rest.test.AbstractControllerIntegrationTest + */ +// Run tests with JUnit 4 and Spring TestContext Framework +@RunWith(SpringRunner.class) +// Specify main class to use to load Spring ApplicationContext +// ALSO tell Spring to start a web server on a random port +// 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, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +// Load DSpace initializers in Spring ApplicationContext (to initialize DSpace Kernel & Configuration) +@ContextConfiguration(initializers = { DSpaceKernelInitializer.class, DSpaceConfigurationInitializer.class }) +public class AbstractWebClientIntegrationTest extends AbstractIntegrationTestWithDatabase { + // (Random) port chosen for test web server + @LocalServerPort + private int port; + + // RestTemplate class with access to test web server + @Autowired + private TestRestTemplate restTemplate; + + /** + * Get client TestRestTemplate for making HTTP requests to test webserver + * @return TestRestTemplate + */ + public TestRestTemplate getClient() { + return restTemplate; + } + + /** + * Return the full URL of a request at a specific path. + * (http://localhost:[port][path]) + * @param path Path (should start with a slash) + * @return full URL + */ + public String getURL(String path) { + return "http://localhost:" + port + path; + } + + /** + * Perform a GET request and return response as a String + * @param path path to perform GET against + * @return ResponseEntity with a String body + */ + public ResponseEntity getResponseAsString(String path) { + return getClient().getForEntity(getURL(path), String.class); + } + + /** + * Perform an authenticated (via Basic Auth) GET request and return response as a String + * @param path path to perform GET against + * @param username Username + * @param password Password + * @return ResponseEntity with a String body + */ + public ResponseEntity getResponseAsString(String path, String username, String password) { + return getClient().withBasicAuth(username, password).getForEntity(getURL(path), String.class); + } +} + diff --git a/dspace-spring-rest/src/test/java/org/dspace/sword/Swordv1IT.java b/dspace-spring-rest/src/test/java/org/dspace/sword/Swordv1IT.java new file mode 100644 index 0000000000..81777148a8 --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/sword/Swordv1IT.java @@ -0,0 +1,60 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ + +package org.dspace.app.sword; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; + +import org.dspace.app.rest.test.AbstractWebClientIntegrationTest; +import org.dspace.services.ConfigurationService; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.TestPropertySource; + +/** + * Integration test to test the /sword endpoint which loads/embeds the SWORD webapp into the REST API. + * This is a AbstractWebClientIntegrationTest because testing the SWORD webapp requires + * running a web server (as the SWORD webapp makes use of Servlets, not Controllers). + * + * @author Tim Donohue + */ +// Ensure the SWORD SERVER IS ENABLED before any tests run. +// This annotation overrides default DSpace config settings loaded into Spring Context +@TestPropertySource(properties = {"sword-server.enabled = true"}) +public class Swordv1IT extends AbstractWebClientIntegrationTest { + + @Autowired + private ConfigurationService configurationService; + + @Test + public void serviceDocumentUnauthorizedTest() throws Exception { + // Attempt to load the ServiceDocument without first authenticating + ResponseEntity response = getResponseAsString("/sword/servicedocument"); + // Expect a 401 response code + assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + } + + @Test + public void serviceDocumentTest() throws Exception { + // Before we can test the ServiceDocument, we must be sure the URL is configured properly + // If the configured URL doesn't match exactly, SWORD will respond with a 404, even if authentication succeeds + configurationService.setProperty("sword-server.servicedocument.url", getURL("/sword/servicedocument")); + + // Attempt to load the ServiceDocument as an Admin user. + ResponseEntity response = getResponseAsString("/sword/servicedocument", + admin.getEmail(), password); + // Expect a 200 response code, and an ATOM UTF-8 document + assertThat(response.getStatusCode(), equalTo(HttpStatus.OK)); + assertThat(response.getHeaders().getContentType().toString(), equalTo("application/atomsvc+xml;charset=UTF-8")); + } + +} + From 56f7cb11bc118257e1b907f13ab0d998dba1b857 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 31 Jan 2019 22:28:24 +0000 Subject: [PATCH 13/52] Remove maven-war-plugin from SWORD POM. It's not needed anymore --- dspace-sword/pom.xml | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 5a07a60e4a..7c83e35552 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -25,28 +25,6 @@ ${basedir}/.. - - - - org.apache.maven.plugins - maven-war-plugin - - true - - WEB-INF/lib/*.jar - WEB-INF/lib/*.jar - - true - - - - prepare-package - - - - - - oracle-support From 6f51195e6cd1871badf970e2848800d82267506b Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 31 Jan 2019 22:28:52 +0000 Subject: [PATCH 14/52] Ensure SWORD integration tests only run if SWORDWebConfig is loaded --- dspace-spring-rest/pom.xml | 2 ++ .../test/AbstractWebClientIntegrationTest.java | 5 +++++ .../src/test/java/org/dspace/sword/Swordv1IT.java | 14 ++++++++++++++ 3 files changed, 21 insertions(+) diff --git a/dspace-spring-rest/pom.xml b/dspace-spring-rest/pom.xml index 611113d355..e06032ae3f 100644 --- a/dspace-spring-rest/pom.xml +++ b/dspace-spring-rest/pom.xml @@ -259,6 +259,8 @@ dspace-services + + org.dspace dspace-sword diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java index 29f81b4510..b4840161dc 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java @@ -15,6 +15,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.embedded.LocalServerPort; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.context.ApplicationContext; import org.springframework.http.ResponseEntity; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; @@ -52,6 +53,10 @@ public class AbstractWebClientIntegrationTest extends AbstractIntegrationTestWit @Autowired private TestRestTemplate restTemplate; + // Spring Application context + @Autowired + protected ApplicationContext applicationContext; + /** * Get client TestRestTemplate for making HTTP requests to test webserver * @return TestRestTemplate diff --git a/dspace-spring-rest/src/test/java/org/dspace/sword/Swordv1IT.java b/dspace-spring-rest/src/test/java/org/dspace/sword/Swordv1IT.java index 81777148a8..066a25eb84 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/sword/Swordv1IT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/sword/Swordv1IT.java @@ -13,6 +13,8 @@ import static org.junit.Assert.assertThat; import org.dspace.app.rest.test.AbstractWebClientIntegrationTest; import org.dspace.services.ConfigurationService; +import org.junit.Assume; +import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; @@ -34,6 +36,18 @@ public class Swordv1IT extends AbstractWebClientIntegrationTest { @Autowired private ConfigurationService configurationService; + @Before + public void onlyRunIfConfigExists() { + // These integration tests REQUIRE that SWORDWebConfig is found/available (as this class deploys SWORD) + // If this class is not available, the below "Assume" will cause all tests to be SKIPPED + // NOTE: SWORDWebConfig is provided by the 'dspace-sword' module + try { + Class.forName("org.dspace.app.configuration.SWORDWebConfig"); + } catch (ClassNotFoundException ce) { + Assume.assumeNoException(ce); + } + } + @Test public void serviceDocumentUnauthorizedTest() throws Exception { // Attempt to load the ServiceDocument without first authenticating From 46a50aeded0d1cfc032f9a5b5e3afbff8c59faac Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 1 Feb 2019 18:46:19 +0000 Subject: [PATCH 15/52] Add basic validation tests for all SWORDv1 endpoints --- .../AbstractWebClientIntegrationTest.java | 20 +++++++ .../test/java/org/dspace/sword/Swordv1IT.java | 53 ++++++++++++++++--- 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java index b4840161dc..26fa7f0ef7 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest.test; +import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.Application; import org.dspace.app.rest.utils.DSpaceConfigurationInitializer; import org.dspace.app.rest.utils.DSpaceKernelInitializer; @@ -16,6 +17,7 @@ import org.springframework.boot.context.embedded.LocalServerPort; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; 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.junit4.SpringRunner; @@ -94,5 +96,23 @@ public class AbstractWebClientIntegrationTest extends AbstractIntegrationTestWit public ResponseEntity getResponseAsString(String path, String username, String password) { return getClient().withBasicAuth(username, password).getForEntity(getURL(path), String.class); } + + /** + * Perform an authenticated (via Basic Auth) POST request and return response as a String. + * @param path path to perform GET against + * @param username Username (may be null to perform an unauthenticated POST) + * @param password Password + * @return ResponseEntity with a String body + */ + public ResponseEntity postResponseAsString(String path, String username, String password, + HttpEntity requestEntity) { + // If username is not empty, perform an authenticated POST. Else attempt without AuthN + if (StringUtils.isNotBlank(username)) { + return getClient().withBasicAuth(username, password).postForEntity(getURL(path), requestEntity, + String.class); + } else { + return getClient().postForEntity(getURL(path), requestEntity, String.class); + } + } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/sword/Swordv1IT.java b/dspace-spring-rest/src/test/java/org/dspace/sword/Swordv1IT.java index 066a25eb84..8e7b38a9af 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/sword/Swordv1IT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/sword/Swordv1IT.java @@ -8,6 +8,7 @@ package org.dspace.app.sword; +import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertThat; @@ -15,6 +16,7 @@ import org.dspace.app.rest.test.AbstractWebClientIntegrationTest; import org.dspace.services.ConfigurationService; import org.junit.Assume; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; @@ -36,6 +38,11 @@ public class Swordv1IT extends AbstractWebClientIntegrationTest { @Autowired private ConfigurationService configurationService; + // All SWORD paths that we test against + private final String SERVICE_DOC_PATH = "/sword/servicedocument"; + private final String DEPOSIT_PATH = "/sword/deposit"; + private final String MEDIA_LINK_PATH = "/sword/media-link"; + @Before public void onlyRunIfConfigExists() { // These integration tests REQUIRE that SWORDWebConfig is found/available (as this class deploys SWORD) @@ -46,29 +53,63 @@ public class Swordv1IT extends AbstractWebClientIntegrationTest { } catch (ClassNotFoundException ce) { Assume.assumeNoException(ce); } + + // Ensure SWORD URL configurations are set correctly (based on our integration test server's paths) + // SWORD validates requests against these configs, and throws a 404 if they don't match the request path + configurationService.setProperty("sword-server.servicedocument.url", getURL(SERVICE_DOC_PATH)); + configurationService.setProperty("sword-server.deposit.url", getURL(DEPOSIT_PATH)); + configurationService.setProperty("sword-server.media-link.url", getURL(MEDIA_LINK_PATH)); } @Test public void serviceDocumentUnauthorizedTest() throws Exception { // Attempt to load the ServiceDocument without first authenticating - ResponseEntity response = getResponseAsString("/sword/servicedocument"); + ResponseEntity response = getResponseAsString(SERVICE_DOC_PATH); // Expect a 401 response code assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); } @Test public void serviceDocumentTest() throws Exception { - // Before we can test the ServiceDocument, we must be sure the URL is configured properly - // If the configured URL doesn't match exactly, SWORD will respond with a 404, even if authentication succeeds - configurationService.setProperty("sword-server.servicedocument.url", getURL("/sword/servicedocument")); - // Attempt to load the ServiceDocument as an Admin user. - ResponseEntity response = getResponseAsString("/sword/servicedocument", + ResponseEntity response = getResponseAsString(SERVICE_DOC_PATH, admin.getEmail(), password); // Expect a 200 response code, and an ATOM UTF-8 document assertThat(response.getStatusCode(), equalTo(HttpStatus.OK)); assertThat(response.getHeaders().getContentType().toString(), equalTo("application/atomsvc+xml;charset=UTF-8")); + + // Check for SWORD version in response body + assertThat(response.getBody(), containsString("1.3")); } + @Test + public void depositUnauthorizedTest() throws Exception { + // Attempt to access /deposit endpoint without sending authentication information + ResponseEntity response = postResponseAsString(DEPOSIT_PATH, null, null, null); + // Expect a 401 response code + assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + } + + @Test + @Ignore + public void depositTest() throws Exception { + // TODO: Actually test a full deposit via SWORD. + // Currently, we are just ensuring the /deposit endpoint exists (see above) and isn't throwing a 404 + } + + @Test + public void mediaLinkUnauthorizedTest() throws Exception { + // Attempt to access /media-link endpoint without sending authentication information + ResponseEntity response = getResponseAsString(MEDIA_LINK_PATH); + // Expect a 401 response code + assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + } + + @Test + @Ignore + public void mediaLinkTest() throws Exception { + // TODO: Actually test a /media-link request. + // Currently, we are just ensuring the /media-link endpoint exists (see above) and isn't throwing a 404 + } } From ca5ac68e2d793d9c60a53eb11bde5ab174f7e496 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 1 Feb 2019 22:50:44 +0000 Subject: [PATCH 16/52] Embed SWORDv2 into Boot webapp. Add basic IT to prove it works. --- dspace-spring-rest/pom.xml | 5 +- .../java/org/dspace/sword2/Swordv2IT.java | 82 ++++++++++ dspace-sword/pom.xml | 6 +- .../app/configuration/SWORDWebConfig.java | 10 ++ dspace-swordv2/pom.xml | 60 +++---- .../app/configuration/SWORDv2WebConfig.java | 143 ++++++++++++++++ .../src/main/webapp/WEB-INF/web.xml | 154 ------------------ dspace/config/modules/swordv2-server.cfg | 38 +++++ dspace/modules/swordv2/pom.xml | 144 ---------------- .../swordv2/src/main/webapp/.gitignore | 0 dspace/pom.xml | 1 - pom.xml | 9 +- 12 files changed, 302 insertions(+), 350 deletions(-) create mode 100644 dspace-spring-rest/src/test/java/org/dspace/sword2/Swordv2IT.java create mode 100644 dspace-swordv2/src/main/java/org/dspace/app/configuration/SWORDv2WebConfig.java delete mode 100644 dspace-swordv2/src/main/webapp/WEB-INF/web.xml delete mode 100644 dspace/modules/swordv2/pom.xml delete mode 100644 dspace/modules/swordv2/src/main/webapp/.gitignore diff --git a/dspace-spring-rest/pom.xml b/dspace-spring-rest/pom.xml index e06032ae3f..d1ae820b42 100644 --- a/dspace-spring-rest/pom.xml +++ b/dspace-spring-rest/pom.xml @@ -28,7 +28,6 @@ @ org.dspace.app.rest.Application - 1.4.4.RELEASE 2.4.0 @@ -265,6 +264,10 @@ org.dspace dspace-sword + + org.dspace + dspace-swordv2 + org.apache.commons diff --git a/dspace-spring-rest/src/test/java/org/dspace/sword2/Swordv2IT.java b/dspace-spring-rest/src/test/java/org/dspace/sword2/Swordv2IT.java new file mode 100644 index 0000000000..e9b11738d4 --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/sword2/Swordv2IT.java @@ -0,0 +1,82 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ + +package org.dspace.app.sword2; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; + +import org.dspace.app.rest.test.AbstractWebClientIntegrationTest; +import org.dspace.services.ConfigurationService; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.TestPropertySource; + +/** + * Integration test to test the /swordv2 endpoint which loads/embeds the SWORDv2 webapp into the REST API. + * This is a AbstractWebClientIntegrationTest because testing the SWORDv2 webapp requires + * running a web server (as the SWORDv2 webapp makes use of Servlets, not Controllers). + * + * @author Tim Donohue + */ +// Ensure the SWORDv2 SERVER IS ENABLED before any tests run. +// This annotation overrides default DSpace config settings loaded into Spring Context +@TestPropertySource(properties = {"swordv2-server.enabled = true"}) +public class Swordv2IT extends AbstractWebClientIntegrationTest { + + @Autowired + private ConfigurationService configurationService; + + // All SWORD v2 paths that we test against + private final String SERVICE_DOC_PATH = "/swordv2/servicedocument"; + + @Before + public void onlyRunIfConfigExists() { + // These integration tests REQUIRE that SWORDv2WebConfig is found/available (as this class deploys SWORDv2) + // If this class is not available, the below "Assume" will cause all tests to be SKIPPED + // NOTE: SWORDv2WebConfig is provided by the 'dspace-swordv2' module + try { + Class.forName("org.dspace.app.configuration.SWORDv2WebConfig"); + } catch (ClassNotFoundException ce) { + Assume.assumeNoException(ce); + } + + // Ensure SWORDv2 URL configurations are set correctly (based on our integration test server's paths) + // SWORDv2 validates requests against these configs, and throws a 404 if they don't match the request path + configurationService.setProperty("swordv2-server.servicedocument.url", getURL(SERVICE_DOC_PATH)); + } + + @Test + public void serviceDocumentUnauthorizedTest() throws Exception { + // Attempt to load the ServiceDocument without first authenticating + ResponseEntity response = getResponseAsString(SERVICE_DOC_PATH); + // Expect a 401 response code + assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + } + + @Test + public void serviceDocumentTest() throws Exception { + // Attempt to load the ServiceDocument as an Admin user. + ResponseEntity response = getResponseAsString(SERVICE_DOC_PATH, + admin.getEmail(), password); + // Expect a 200 response code, and an ATOM UTF-8 document + assertThat(response.getStatusCode(), equalTo(HttpStatus.OK)); + assertThat(response.getHeaders().getContentType().toString(), + equalTo("application/atomserv+xml;charset=UTF-8")); + + // Check for SWORD version in response body + assertThat(response.getBody(), + containsString("2.0")); + } +} + diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 7c83e35552..938e9dbb04 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -6,7 +6,7 @@ jar DSpace SWORD - DSpace SWORD Deposit Service Provider Web Application + DSpace SWORD Deposit Service Provider Extension + org.springframework.boot spring-boot-starter-web - 1.4.4.RELEASE + ${spring-boot.version} diff --git a/dspace-sword/src/main/java/org/dspace/app/configuration/SWORDWebConfig.java b/dspace-sword/src/main/java/org/dspace/app/configuration/SWORDWebConfig.java index 4d1578385b..37b6d651a8 100644 --- a/dspace-sword/src/main/java/org/dspace/app/configuration/SWORDWebConfig.java +++ b/dspace-sword/src/main/java/org/dspace/app/configuration/SWORDWebConfig.java @@ -40,6 +40,12 @@ public class SWORDWebConfig { @Value("${sword-server.authentication-method:Basic}") private String authenticationMethod; + /** + * Initialize all required Context Parameters (i.e. in web.xml), based on configurations above. + *

+ * This bean is only loaded when sword-server.enabled = true + * @return ServletContextInitializer which includes all required params + */ @Bean @ConditionalOnProperty("sword-server.enabled") public ServletContextInitializer swordv1ContextInitializer() { @@ -49,6 +55,10 @@ public class SWORDWebConfig { }; } + // Servlet Beans. All of the below bean definitions map servlets to respond to specific URL patterns + // These are the combined equivalent of and in web.xml + // All beans are only loaded when sword-server.enabled = true + @Bean @ConditionalOnProperty("sword-server.enabled") public ServletRegistrationBean swordv1ServiceDocumentBean() { diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 6ac1a15b5d..998ac3436b 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -3,9 +3,9 @@ 4.0.0 org.dspace dspace-swordv2 - war + jar DSpace SWORD v2 - DSpace SWORD v2 Deposit Service Provider Web Application + DSpace SWORD v2 Deposit Service Provider Extension - WEB-INF/lib/*.jar - WEB-INF/lib/*.jar - - true - - - - prepare-package - - - - - - - oracle-support @@ -84,6 +61,8 @@ javax.servlet-api provided + + org.swordapp sword2-server @@ -99,26 +78,30 @@ javax.servlet servlet-api - - log4j + + log4j log4j - - - org.slf4j - slf4j-log4j12 - + + + org.slf4j + slf4j-log4j12 + - - org.swordapp - sword2-server - 1.0 - war - + + org.dspace dspace-api + + + + org.springframework.boot + spring-boot-starter-web + ${spring-boot.version} + + org.apache.logging.log4j log4j-api @@ -135,7 +118,6 @@ org.apache.abdera abdera-client 1.1.3 - org.apache.ws.commons.axiom diff --git a/dspace-swordv2/src/main/java/org/dspace/app/configuration/SWORDv2WebConfig.java b/dspace-swordv2/src/main/java/org/dspace/app/configuration/SWORDv2WebConfig.java new file mode 100644 index 0000000000..ece5094a61 --- /dev/null +++ b/dspace-swordv2/src/main/java/org/dspace/app/configuration/SWORDv2WebConfig.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.app.configuration; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.web.servlet.ServletContextInitializer; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * SWORDv2 webapp configuration. Replaces the old web.xml + *

+ * This @Configuration class is automatically discovered by Spring via a @ComponentScan. + *

+ * All SWORDv2 web configurations (beans) can be enabled or disabled by setting "swordv2-server.enabled" + * to true or false, respectively (in your DSpace configuration). Default is "false". + *

+ * All @Value annotated configurations below can also be overridden in your DSpace configuration. + * + * @author Tim Donohue + */ +@Configuration +public class SWORDv2WebConfig { + // Path where SWORDv2 should be deployed (when enabled). Defaults to "swordv2" + @Value("${swordv2-server.path:swordv2}") + private String swordv2Path; + + // ServiceDocumentManager server implementation class name (default: org.dspace.sword2.ServiceDocumentManagerDSpace) + @Value("${swordv2-server.service-document-impl:org.dspace.sword2.ServiceDocumentManagerDSpace}") + private String serviceDocImpl; + + // CollectionListManager server implementation class name (default: org.dspace.sword2.CollectionListManagerDSpace) + @Value("${swordv2-server.collection-list-impl:org.dspace.sword2.CollectionListManagerDSpace}") + private String collectionListImpl; + + // CollectionDepositManager server implementation class name + // (default: org.dspace.sword2.CollectionDepositManagerDSpace) + @Value("${swordv2-server.collection-deposit-impl:org.dspace.sword2.CollectionDepositManagerDSpace}") + private String collectionDepositImpl; + + // MediaResourceManager server implementation class name (default: org.dspace.sword2.MediaResourceManagerDSpace) + @Value("${swordv2-server.media-resource-impl:org.dspace.sword2.MediaResourceManagerDSpace}") + private String mediaResourceImpl; + + // ContainerManager server implementation class name (default: org.dspace.sword2.ContainerManagerDSpace) + @Value("${swordv2-server.container-impl:org.dspace.sword2.ContainerManagerDSpace}") + private String containerImpl; + + // StatementManager server implementation class name (default: org.dspace.sword2.StatementManagerDSpace) + @Value("${swordv2-server.statement-impl:org.dspace.sword2.StatementManagerDSpace}") + private String statementImpl; + + // SwordConfiguration server implementation class name (default: org.dspace.sword2.SwordConfigurationDSpace) + @Value("${swordv2-server.config-impl:org.dspace.sword2.SwordConfigurationDSpace}") + private String configImpl; + + // Authentication Method. Defaults to "Basic" + @Value("${swordv2-server.auth-type:Basic}") + private String authenticationMethod; + + /** + * Initialize all required Context Parameters (i.e. in web.xml), based on configurations above. + *

+ * This bean is only loaded when swordv2-server.enabled = true + * @return ServletContextInitializer which includes all required params + */ + @Bean + @ConditionalOnProperty("swordv2-server.enabled") + public ServletContextInitializer swordv2ContextInitializer() { + return servletContext -> { + servletContext.setInitParameter("service-document-impl", serviceDocImpl); + servletContext.setInitParameter("collection-list-impl", collectionListImpl); + servletContext.setInitParameter("collection-deposit-impl", collectionDepositImpl); + servletContext.setInitParameter("media-resource-impl", mediaResourceImpl); + servletContext.setInitParameter("container-impl", containerImpl); + servletContext.setInitParameter("statement-impl", statementImpl); + servletContext.setInitParameter("config-impl", configImpl); + servletContext.setInitParameter("authentication-method", authenticationMethod); + }; + } + + // Servlet Beans. All of the below bean definitions map servlets to respond to specific URL patterns + // These are the combined equivalent of and in web.xml + // All beans are only loaded when swordv2-server.enabled = true + + @Bean + @ConditionalOnProperty("swordv2-server.enabled") + public ServletRegistrationBean swordv2ServiceDocumentBean() { + ServletRegistrationBean bean = + new ServletRegistrationBean(new org.swordapp.server.servlets.ServiceDocumentServletDefault(), + "/" + swordv2Path + "/servicedocument/*"); + bean.setLoadOnStartup(1); + return bean; + } + + @Bean + @ConditionalOnProperty("swordv2-server.enabled") + public ServletRegistrationBean swordv2CollectionBean() { + ServletRegistrationBean bean = + new ServletRegistrationBean( new org.swordapp.server.servlets.CollectionServletDefault(), + "/" + swordv2Path + "/collection/*"); + bean.setLoadOnStartup(1); + return bean; + } + + @Bean + @ConditionalOnProperty("swordv2-server.enabled") + public ServletRegistrationBean swordv2MediaResourceBean() { + ServletRegistrationBean bean = + new ServletRegistrationBean( new org.swordapp.server.servlets.MediaResourceServletDefault(), + "/" + swordv2Path + "/edit-media/*"); + bean.setLoadOnStartup(1); + return bean; + } + + @Bean + @ConditionalOnProperty("swordv2-server.enabled") + public ServletRegistrationBean swordv2ContainerBean() { + ServletRegistrationBean bean = + new ServletRegistrationBean( new org.swordapp.server.servlets.ContainerServletDefault(), + "/" + swordv2Path + "/edit/*"); + bean.setLoadOnStartup(1); + return bean; + } + + @Bean + @ConditionalOnProperty("swordv2-server.enabled") + public ServletRegistrationBean swordv2StatementBean() { + ServletRegistrationBean bean = + new ServletRegistrationBean( new org.swordapp.server.servlets.StatementServletDefault(), + "/" + swordv2Path + "/statement/*"); + bean.setLoadOnStartup(1); + return bean; + } +} + diff --git a/dspace-swordv2/src/main/webapp/WEB-INF/web.xml b/dspace-swordv2/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index f7794795dd..0000000000 --- a/dspace-swordv2/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,154 +0,0 @@ - - - - - DSpace SWORD 2.0 Server - - - - The location of the DSpace home directory - dspace.dir - ${dspace.dir} - - - - - - The ServiceDocumentManager server implementation class name - service-document-impl - org.dspace.sword2.ServiceDocumentManagerDSpace - - - - log4jConfiguration - ${dspace.dir}/config/log4j2.xml - - The location of the Log4J configuration - - - - - - The CollectionListManager server implementation class name - collection-list-impl - org.dspace.sword2.CollectionListManagerDSpace - - - - The CollectionDepositManager server implementation class name - collection-deposit-impl - org.dspace.sword2.CollectionDepositManagerDSpace - - - - The MediaResourceManager server implementation class name - media-resource-impl - org.dspace.sword2.MediaResourceManagerDSpace - - - - The ContainerManager server implementation class name - container-impl - org.dspace.sword2.ContainerManagerDSpace - - - - The StatementManager server implementation class name - statement-impl - org.dspace.sword2.StatementManagerDSpace - - - - - The SwordConfiguration server implementation class name - config-impl - org.dspace.sword2.SwordConfigurationDSpace - - - - The type of authentication used : [Basic|None] - authentication-method - Basic - - - - - - org.dspace.app.util.DSpaceContextListener - - - - - org.dspace.servicemanager.servlet.DSpaceKernelServletContextListener - - - - - - - servicedocument - org.swordapp.server.servlets.ServiceDocumentServletDefault - - - - collection - org.swordapp.server.servlets.CollectionServletDefault - - - - mediaresource - org.swordapp.server.servlets.MediaResourceServletDefault - - - - container - org.swordapp.server.servlets.ContainerServletDefault - - - - statement - org.swordapp.server.servlets.StatementServletDefault - - - - - - servicedocument - /servicedocument/* - - - - collection - /collection/* - - - - mediaresource - /edit-media/* - - - - container - /edit/* - - - - statement - /statement/* - - - diff --git a/dspace/config/modules/swordv2-server.cfg b/dspace/config/modules/swordv2-server.cfg index 6e6cfd1c39..200d100f8b 100644 --- a/dspace/config/modules/swordv2-server.cfg +++ b/dspace/config/modules/swordv2-server.cfg @@ -6,6 +6,18 @@ # SWORD 2.0 protocol) # #---------------------------------------------------------------# +# Whether or not to enable the SWORD v2 module +# When "true", the SWORD v2 module is accessible on ${swordv2-server.path} +# When "false" or commented out, SWORD v2 is disabled/inaccessible. +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +#swordv2-server.enabled = true + + # Path where SWORD v2 module is available (in the Spring REST webapp) +# Defaults to "swordv2", which means the SWORD v2 module would be available +# at ${dspace.restURL}/swordv2/ +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +#swordv2-server.path = swordv2 + # the base url of the sword 2.0 system # # the default if {dspace.url}/swordv2 @@ -194,6 +206,32 @@ swordv2-server.generator.version = 2.0 # Other valid values: 'None' swordv2-server.auth-type = Basic +# SWORD v2 server implementation classes +# These are the default implementation classes to use for SWORD functions. +# Uncomment if you wish to change the implementation class. +# (Requires reboot of servlet container, e.g. Tomcat, to reload these settings) +# +# ServiceDocumentManager server implementation +#swordv2-server.service-document-impl = org.dspace.sword2.ServiceDocumentManagerDSpace +# +# CollectionListManager server implementation class +#swordv2-server.collection-list-impl = org.dspace.sword2.CollectionListManagerDSpace +# +# CollectionDepositManager server implementation class +#swordv2-server.collection-deposit-impl = org.dspace.sword2.CollectionDepositManagerDSpace +# +# MediaResourceManager server implementation class +#swordv2-server.media-resource-impl = org.dspace.sword2.MediaResourceManagerDSpace +# +# ContainerManager server implementation class name +#swordv2-server.container-impl = org.dspace.sword2.ContainerManagerDSpace +# +# StatementManager server implementation class +#swordv2-server.statement-impl = org.dspace.sword2.StatementManagerDSpace +# +# SwordConfiguration server implementation class +#swordv2-server.config-impl = org.dspace.sword2.SwordConfigurationDSpace + # The location where uploaded files and packages are # stored while being processed swordv2-server.upload.tempdir = ${upload.temp.dir} diff --git a/dspace/modules/swordv2/pom.xml b/dspace/modules/swordv2/pom.xml deleted file mode 100644 index 11f0b684b5..0000000000 --- a/dspace/modules/swordv2/pom.xml +++ /dev/null @@ -1,144 +0,0 @@ - - 4.0.0 - org.dspace.modules - swordv2 - war - DSpace SWORD v2 :: Local Customizations - - This project allows you to overlay your own local SWORD v2 customizations - on top of the default SWORD v2 web application provided with DSpace. - - - - - org.dspace - modules - 7.0-SNAPSHOT - .. - - - - - ${basedir}/../../.. - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - unpack - prepare-package - - unpack-dependencies - - - org.dspace.modules - additions - - ${project.build.directory}/additions - META-INF/** - - - - - - org.apache.maven.plugins - maven-war-plugin - - false - - true - - - - ${project.build.directory}/additions - WEB-INF/classes - - - - - - prepare-package - - - - - - - - - oracle-support - - - db.name - oracle - - - - - com.oracle - ojdbc6 - - - - - postgres-support - - - !db.name - - - - - org.postgresql - postgresql - - - - - - - - - org.dspace.modules - additions - - - - - org.dspace - dspace-swordv2 - war - - - - - org.dspace - dspace-swordv2 - jar - classes - - - - javax.servlet - javax.servlet-api - provided - - - - - diff --git a/dspace/modules/swordv2/src/main/webapp/.gitignore b/dspace/modules/swordv2/src/main/webapp/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/dspace/pom.xml b/dspace/pom.xml index 8b4427a508..da6059b897 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -232,7 +232,6 @@ org.dspace dspace-swordv2 - classes compile diff --git a/pom.xml b/pom.xml index c46616e5da..528b196e2b 100644 --- a/pom.xml +++ b/pom.xml @@ -37,6 +37,7 @@ https://issues.apache.org/jira/browse/SOLR-12858 --> 7.3.1 4.3.6.RELEASE + 1.4.4.RELEASE ${basedir} @@ -946,14 +947,6 @@ org.dspace dspace-swordv2 7.0-SNAPSHOT - jar - classes - - - org.dspace - dspace-swordv2 - 7.0-SNAPSHOT - war org.dspace From 06f8a2a6e28fc0161ef2d5aa8bc2141a971e5c63 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 8 Feb 2019 21:59:03 +0000 Subject: [PATCH 17/52] More ITs for SWORDv2. Ensure all SWORDv2 endpoints have a sanity test. --- .../java/org/dspace/sword2/Swordv2IT.java | 71 ++++++++++++++++++- 1 file changed, 68 insertions(+), 3 deletions(-) diff --git a/dspace-spring-rest/src/test/java/org/dspace/sword2/Swordv2IT.java b/dspace-spring-rest/src/test/java/org/dspace/sword2/Swordv2IT.java index e9b11738d4..cc672df9fc 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/sword2/Swordv2IT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/sword2/Swordv2IT.java @@ -16,6 +16,7 @@ import org.dspace.app.rest.test.AbstractWebClientIntegrationTest; import org.dspace.services.ConfigurationService; import org.junit.Assume; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; @@ -39,6 +40,10 @@ public class Swordv2IT extends AbstractWebClientIntegrationTest { // All SWORD v2 paths that we test against private final String SERVICE_DOC_PATH = "/swordv2/servicedocument"; + private final String COLLECTION_PATH = "/swordv2/collection"; + private final String MEDIA_RESOURCE_PATH = "/swordv2/edit-media"; + private final String CONTAINER_PATH = "/swordv2/edit"; + private final String STATEMENT_PATH = "/swordv2/statement"; @Before public void onlyRunIfConfigExists() { @@ -58,7 +63,7 @@ public class Swordv2IT extends AbstractWebClientIntegrationTest { @Test public void serviceDocumentUnauthorizedTest() throws Exception { - // Attempt to load the ServiceDocument without first authenticating + // Attempt to GET the ServiceDocument without first authenticating ResponseEntity response = getResponseAsString(SERVICE_DOC_PATH); // Expect a 401 response code assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); @@ -66,7 +71,7 @@ public class Swordv2IT extends AbstractWebClientIntegrationTest { @Test public void serviceDocumentTest() throws Exception { - // Attempt to load the ServiceDocument as an Admin user. + // Attempt to GET the ServiceDocument as an Admin user. ResponseEntity response = getResponseAsString(SERVICE_DOC_PATH, admin.getEmail(), password); // Expect a 200 response code, and an ATOM UTF-8 document @@ -74,9 +79,69 @@ public class Swordv2IT extends AbstractWebClientIntegrationTest { assertThat(response.getHeaders().getContentType().toString(), equalTo("application/atomserv+xml;charset=UTF-8")); - // Check for SWORD version in response body + // Check for correct SWORD version in response body assertThat(response.getBody(), containsString("2.0")); } + + @Test + public void collectionUnauthorizedTest() throws Exception { + // Attempt to POST to /collection endpoint without sending authentication information + ResponseEntity response = postResponseAsString(COLLECTION_PATH, null, null, null); + // Expect a 401 response code + assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + } + + @Test + @Ignore + public void collectionTest() throws Exception { + // TODO: Actually test collection endpoint via SWORDv2. + // Currently, we are just ensuring the /collection endpoint exists (see above) and isn't throwing a 404 + } + + @Test + public void mediaResourceUnauthorizedTest() throws Exception { + // Attempt to POST to /mediaresource endpoint without sending authentication information + ResponseEntity response = postResponseAsString(MEDIA_RESOURCE_PATH, null, null, null); + // Expect a 401 response code + assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + } + + @Test + @Ignore + public void mediaResourceTest() throws Exception { + // TODO: Actually test this endpoint via SWORDv2. + // Currently, we are just ensuring the /mediaresource endpoint exists (see above) and isn't throwing a 404 + } + + @Test + public void containerUnauthorizedTest() throws Exception { + // Attempt to POST to /container endpoint without sending authentication information + ResponseEntity response = postResponseAsString(CONTAINER_PATH, null, null, null); + // Expect a 401 response code + assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + } + + @Test + @Ignore + public void containerTest() throws Exception { + // TODO: Actually test this endpoint via SWORDv2. + // Currently, we are just ensuring the /container endpoint exists (see above) and isn't throwing a 404 + } + + @Test + public void statementUnauthorizedTest() throws Exception { + // Attempt to GET /statement endpoint without sending authentication information + ResponseEntity response = getResponseAsString(STATEMENT_PATH); + // Expect a 401 response code + assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + } + + @Test + @Ignore + public void statementTest() throws Exception { + // TODO: Actually test this endpoint via SWORDv2. + // Currently, we are just ensuring the /statement endpoint exists (see above) and isn't throwing a 404 + } } From 27512ad382a8823cc011daa93d57d667b3a07213 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 13 Feb 2019 21:49:07 +0000 Subject: [PATCH 18/52] Correct location of SWORD ITs --- .../src/test/java/org/dspace/{ => app}/sword/Swordv1IT.java | 0 .../src/test/java/org/dspace/{ => app}/sword2/Swordv2IT.java | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename dspace-spring-rest/src/test/java/org/dspace/{ => app}/sword/Swordv1IT.java (100%) rename dspace-spring-rest/src/test/java/org/dspace/{ => app}/sword2/Swordv2IT.java (100%) diff --git a/dspace-spring-rest/src/test/java/org/dspace/sword/Swordv1IT.java b/dspace-spring-rest/src/test/java/org/dspace/app/sword/Swordv1IT.java similarity index 100% rename from dspace-spring-rest/src/test/java/org/dspace/sword/Swordv1IT.java rename to dspace-spring-rest/src/test/java/org/dspace/app/sword/Swordv1IT.java diff --git a/dspace-spring-rest/src/test/java/org/dspace/sword2/Swordv2IT.java b/dspace-spring-rest/src/test/java/org/dspace/app/sword2/Swordv2IT.java similarity index 100% rename from dspace-spring-rest/src/test/java/org/dspace/sword2/Swordv2IT.java rename to dspace-spring-rest/src/test/java/org/dspace/app/sword2/Swordv2IT.java From cb86effb382e24de4076bb1ccb74ade0c60e450d Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 13 Feb 2019 22:04:16 +0000 Subject: [PATCH 19/52] Correct comments to be more accurate --- .../src/test/java/org/dspace/app/sword/Swordv1IT.java | 8 +++++--- .../src/test/java/org/dspace/app/sword2/Swordv2IT.java | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/sword/Swordv1IT.java b/dspace-spring-rest/src/test/java/org/dspace/app/sword/Swordv1IT.java index 8e7b38a9af..24244e1773 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/sword/Swordv1IT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/sword/Swordv1IT.java @@ -24,9 +24,11 @@ import org.springframework.http.ResponseEntity; import org.springframework.test.context.TestPropertySource; /** - * Integration test to test the /sword endpoint which loads/embeds the SWORD webapp into the REST API. - * This is a AbstractWebClientIntegrationTest because testing the SWORD webapp requires - * running a web server (as the SWORD webapp makes use of Servlets, not Controllers). + * Integration test to verify that the /sword endpoint is responding as a valid SWORD endpoint. + * This tests that our dspace-sword module is running at this endpoint. + *

+ * This is a AbstractWebClientIntegrationTest because testing dspace-sword requires + * running a web server (as dspace-sword makes use of Servlets, not Controllers). * * @author Tim Donohue */ diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/sword2/Swordv2IT.java b/dspace-spring-rest/src/test/java/org/dspace/app/sword2/Swordv2IT.java index cc672df9fc..95ec762514 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/sword2/Swordv2IT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/sword2/Swordv2IT.java @@ -24,9 +24,11 @@ import org.springframework.http.ResponseEntity; import org.springframework.test.context.TestPropertySource; /** - * Integration test to test the /swordv2 endpoint which loads/embeds the SWORDv2 webapp into the REST API. - * This is a AbstractWebClientIntegrationTest because testing the SWORDv2 webapp requires - * running a web server (as the SWORDv2 webapp makes use of Servlets, not Controllers). + * Integration test to verify the /swordv2 endpoint is responding as a valid SWORDv2 endpoint. + * This tests that our dspace-swordv2 module is running at this endpoint. + *

+ * This is a AbstractWebClientIntegrationTest because testing dspace-swordv2 requires + * running a web server (as dspace-swordv2 makes use of Servlets, not Controllers). * * @author Tim Donohue */ From 89400609aa48b11c3bc23025a4664afb2ab533b9 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 15 Feb 2019 22:21:44 +0000 Subject: [PATCH 20/52] Embed OAI-PMH into Boot webapp. Add basic ITs to prove it works. --- dspace-oai/pom.xml | 91 +++------- .../configuration/OAIWebConfig.java} | 26 ++- .../controller/DSpaceOAIDataProvider.java | 5 + .../WEB-INF/spring/applicationContext.xml | 60 ------- dspace-oai/src/main/webapp/WEB-INF/web.xml | 83 --------- dspace-spring-rest/pom.xml | 4 + .../java/org/dspace/app/oai/OAIpmhIT.java | 86 +++++++++ dspace/modules/oai/pom.xml | 169 ------------------ dspace/modules/oai/src/main/webapp/.gitignore | 0 dspace/pom.xml | 3 - pom.xml | 8 - 11 files changed, 134 insertions(+), 401 deletions(-) rename dspace-oai/src/main/java/org/dspace/{xoai/app/DSpaceWebappConfiguration.java => app/configuration/OAIWebConfig.java} (77%) delete mode 100644 dspace-oai/src/main/webapp/WEB-INF/spring/applicationContext.xml delete mode 100644 dspace-oai/src/main/webapp/WEB-INF/web.xml create mode 100644 dspace-spring-rest/src/test/java/org/dspace/app/oai/OAIpmhIT.java delete mode 100644 dspace/modules/oai/pom.xml delete mode 100644 dspace/modules/oai/src/main/webapp/.gitignore diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 885c045227..716f222461 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -2,9 +2,9 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 dspace-oai - war + jar DSpace OAI-PMH - DSpace OAI-PMH Web Application and API + DSpace OAI-PMH Extension dspace-parent @@ -22,28 +22,12 @@ - - org.apache.maven.plugins - maven-war-plugin - - true - - WEB-INF/lib/*.jar - WEB-INF/lib/*.jar - - true - - - - prepare-package - - - com.mycila license-maven-plugin + src/main/webapp/** **/*.xsl @@ -107,42 +91,27 @@ org.apache.commons commons-lang3 - - log4j - log4j - - - org.slf4j - slf4j-log4j12 - + + log4j + log4j + + + org.slf4j + slf4j-log4j12 + + + + org.codehaus.woodstox + wstx-asl + - + - javax.inject - javax.inject - 1 - - - org.springframework - spring-webmvc - ${spring.version} - - - org.springframework - spring-beans - ${spring.version} - - - org.springframework - spring-context - ${spring.version} - - - org.springframework - spring-web - ${spring.version} + org.springframework.boot + spring-boot-starter-web + ${spring-boot.version} @@ -294,24 +263,4 @@ test - - - - DSpace @ Lyncode - dspace@lyncode.com - Lyncode - http://www.lyncode.com - - - helix84 - Ivan Másar - helix84@centrum.sk - - - Ariel J. Lira - arieljlira@gmail.com - SeDiCI - http://sedici.unlp.edu.ar - - diff --git a/dspace-oai/src/main/java/org/dspace/xoai/app/DSpaceWebappConfiguration.java b/dspace-oai/src/main/java/org/dspace/app/configuration/OAIWebConfig.java similarity index 77% rename from dspace-oai/src/main/java/org/dspace/xoai/app/DSpaceWebappConfiguration.java rename to dspace-oai/src/main/java/org/dspace/app/configuration/OAIWebConfig.java index 8ad41abb65..c4b4bc7ace 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/app/DSpaceWebappConfiguration.java +++ b/dspace-oai/src/main/java/org/dspace/app/configuration/OAIWebConfig.java @@ -5,11 +5,11 @@ * * http://www.dspace.org/license/ */ -package org.dspace.xoai.app; - +package org.dspace.app.configuration; import static java.lang.Integer.MAX_VALUE; import com.lyncode.jtwig.mvc.JtwigViewResolver; +import org.dspace.xoai.app.BasicConfiguration; import org.dspace.xoai.services.api.xoai.ItemRepositoryResolver; import org.dspace.xoai.services.impl.xoai.DSpaceItemRepositoryResolver; import org.springframework.context.annotation.Bean; @@ -22,13 +22,25 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; -@Import( { - BasicConfiguration.class -}) +/** + * OAI-PMH webapp configuration. Replaces the old web.xml + *

+ * This @Configuration class is automatically discovered by Spring Boot via a @ComponentScan + * on the org.dspace.app.configuration package. + *

+ * + * + * @author Tim Donohue + */ @Configuration +// Import WebMVC configuration (and allow us to extend WebMvcConfigurerAdapter) @EnableWebMvc +// Import additional configuration and beans from BasicConfiguration +@Import(BasicConfiguration.class) +// Scan for controllers in this package @ComponentScan("org.dspace.xoai.controller") -public class DSpaceWebappConfiguration extends WebMvcConfigurerAdapter { +public class OAIWebConfig extends WebMvcConfigurerAdapter { + private static final String TWIG_HTML_EXTENSION = ".twig.html"; private static final String VIEWS_LOCATION = "/WEB-INF/views/"; @@ -58,5 +70,5 @@ public class DSpaceWebappConfiguration extends WebMvcConfigurerAdapter { public ItemRepositoryResolver xoaiItemRepositoryResolver() { return new DSpaceItemRepositoryResolver(); } - } + diff --git a/dspace-oai/src/main/java/org/dspace/xoai/controller/DSpaceOAIDataProvider.java b/dspace-oai/src/main/java/org/dspace/xoai/controller/DSpaceOAIDataProvider.java index 96e82d8eae..6f33331a1d 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/controller/DSpaceOAIDataProvider.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/controller/DSpaceOAIDataProvider.java @@ -40,6 +40,7 @@ import org.dspace.xoai.services.api.xoai.ItemRepositoryResolver; import org.dspace.xoai.services.api.xoai.SetRepositoryResolver; import org.dspace.xoai.services.impl.xoai.DSpaceResumptionTokenFormatter; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.PathVariable; @@ -49,6 +50,10 @@ import org.springframework.web.bind.annotation.RequestMapping; * @author Lyncode Development Team (dspace at lyncode dot com) */ @Controller +// Use the configured "oai.path" for all requests, or "/oai" by default +@RequestMapping("/${oai.path:oai}") +// Only enable this controller if "oai.enabled=true" +@ConditionalOnProperty("oai.enabled") public class DSpaceOAIDataProvider { private static final Logger log = getLogger(DSpaceOAIDataProvider.class); diff --git a/dspace-oai/src/main/webapp/WEB-INF/spring/applicationContext.xml b/dspace-oai/src/main/webapp/WEB-INF/spring/applicationContext.xml deleted file mode 100644 index 0b6f2cde61..0000000000 --- a/dspace-oai/src/main/webapp/WEB-INF/spring/applicationContext.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/dspace-oai/src/main/webapp/WEB-INF/web.xml b/dspace-oai/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index d731a0c659..0000000000 --- a/dspace-oai/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - XOAI Data Provider - - - The location of the DSpace home directory - dspace.dir - ${dspace.dir} - - - - log4jConfiguration - ${dspace.dir}/config/log4j2.xml - The location of the Log4J configuration - - - - - - contextConfigLocation - /WEB-INF/spring/*.xml - - - - - org.dspace.app.util.DSpaceContextListener - - - - - - org.dspace.servicemanager.servlet.DSpaceKernelServletContextListener - - - - - - org.dspace.app.util.DSpaceWebappListener - - - - - org.springframework.web.context.ContextLoaderListener - - - - oai - org.springframework.web.servlet.DispatcherServlet - - - contextClass - - org.springframework.web.context.support.AnnotationConfigWebApplicationContext - - - - - - contextConfigLocation - org.dspace.xoai.app.DSpaceWebappConfiguration - - - - - - oai - /* - - - diff --git a/dspace-spring-rest/pom.xml b/dspace-spring-rest/pom.xml index d1ae820b42..581c86062e 100644 --- a/dspace-spring-rest/pom.xml +++ b/dspace-spring-rest/pom.xml @@ -260,6 +260,10 @@ + + org.dspace + dspace-oai + org.dspace dspace-sword diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/oai/OAIpmhIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/oai/OAIpmhIT.java new file mode 100644 index 0000000000..4c343bf518 --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/oai/OAIpmhIT.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.oai; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.xpath; + +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.services.ConfigurationService; +import org.junit.Assume; +import org.junit.Before; +//import org.junit.Ignore; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.TestPropertySource; + +/** + * Integration test to verify the /oai endpoint is responding as a valid OAI-PMH endpoint. + * This tests that our dspace-oai module is running at this endpoint. + *

+ * This is an AbstractControllerIntegrationTest because dspace-oai makes use of Controllers. + * + * @author Tim Donohue + */ +// Ensure the OAI SERVER IS ENABLED before any tests run. +// This annotation overrides default DSpace config settings loaded into Spring Context +@TestPropertySource(properties = {"oai.enabled = true"}) +public class OAIpmhIT extends AbstractControllerIntegrationTest { + + @Autowired + private ConfigurationService configurationService; + + // All OAI-PMH paths that we test against + private final String ROOT_PATH = "/oai"; + private final String ROOT_REQUEST = "/oai/request"; + + @Before + public void onlyRunIfConfigExists() { + // These integration tests REQUIRE that OAIWebConfig is found/available (as this class deploys OAI) + // If this class is not available, the below "Assume" will cause all tests to be SKIPPED + // NOTE: OAIWebConfig is provided by the 'dspace-oai' module + try { + Class.forName("org.dspace.app.configuration.OAIWebConfig"); + } catch (ClassNotFoundException ce) { + Assume.assumeNoException(ce); + } + } + + @Test + public void oaiRootTest() throws Exception { + // Attempt to call the root endpoint + getClient().perform(get(ROOT_PATH)) + // Expect a 400 response code (OAI requires a context) + .andExpect(status().isOk()) + ; + } + + + @Test + public void oaiRootIdentifyTest() throws Exception { + // Attempt to make an Identify request to root context + getClient().perform(get(ROOT_REQUEST).param("verb", "Identify")) + // Expect a 200 response code + .andExpect(status().isOk()) + // Expect the content type to be "text/xml" + .andExpect(content().contentType("text/xml")) + // Expect oai + .andExpect(xpath("OAI-PMH/Identify/description/oai-identifier/scheme").string("oai")) + // Expect protocol version 2.0 + .andExpect(xpath("OAI-PMH/Identify/protocolVersion").string("2.0")) + // Expect repositoryName to be the same as "dspace.name" config + .andExpect(xpath("OAI-PMH/Identify/repositoryName") + .string(configurationService.getProperty("dspace.name"))) + ; + } + +} + diff --git a/dspace/modules/oai/pom.xml b/dspace/modules/oai/pom.xml deleted file mode 100644 index e36adfafcc..0000000000 --- a/dspace/modules/oai/pom.xml +++ /dev/null @@ -1,169 +0,0 @@ - - 4.0.0 - org.dspace.modules - oai - war - DSpace OAI-PMH :: Local Customizations - - This project allows you to overlay your own local OAI customizations - on top of the default OAI-PMH web application provided with DSpace. - - - - modules - org.dspace - 7.0-SNAPSHOT - .. - - - - - ${basedir}/../../.. - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - unpack - prepare-package - - unpack-dependencies - - - org.dspace.modules - additions - - ${project.build.directory}/additions - META-INF/** - - - - - - org.apache.maven.plugins - maven-war-plugin - - false - - true - - - - ${project.build.directory}/additions - WEB-INF/classes - - - - - - prepare-package - - - - - - - - - oracle-support - - - db.name - oracle - - - - - com.oracle - ojdbc6 - - - - - - - - org.dspace.modules - additions - - - com.lyncode - builder-commons - - - - - org.dspace - dspace-oai - war - - - org.dspace - dspace-oai - jar - classes - - - com.lyncode - builder-commons - - - com.google.guava - guava - - - - - javax.servlet - javax.servlet-api - provided - - - - - com.lyncode - builder-commons - 1.0.2 - - - com.google.guava - guava - - - - - - - - org.hamcrest - hamcrest-all - compile - - - - - - lyncode - dspace@lyncode.com - DSpace @ Lyncode - http://www.lyncode.com - - - diff --git a/dspace/modules/oai/src/main/webapp/.gitignore b/dspace/modules/oai/src/main/webapp/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/dspace/pom.xml b/dspace/pom.xml index da6059b897..0c6ea85f78 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -198,7 +198,6 @@ org.dspace dspace-oai - classes compile @@ -288,8 +287,6 @@ org.dspace dspace-oai - jar - classes com.lyncode diff --git a/pom.xml b/pom.xml index 528b196e2b..4bf4365de5 100644 --- a/pom.xml +++ b/pom.xml @@ -952,14 +952,6 @@ org.dspace dspace-oai 7.0-SNAPSHOT - jar - classes - - - org.dspace - dspace-oai - 7.0-SNAPSHOT - war org.dspace From 7c83a58a204228587d1ba162d32c34a27a3cc7a8 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 15 Feb 2019 22:22:31 +0000 Subject: [PATCH 21/52] Minor bug fix to OAI-PMH found via ITs in previous commit --- .../services/impl/DSpaceEarliestDateResolver.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/DSpaceEarliestDateResolver.java b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/DSpaceEarliestDateResolver.java index de8dc0d4ad..ba51c62d1b 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/DSpaceEarliestDateResolver.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/DSpaceEarliestDateResolver.java @@ -10,6 +10,8 @@ package org.dspace.xoai.services.impl; import java.sql.SQLException; import java.util.Date; +import javax.persistence.NoResultException; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.content.MetadataValue; @@ -30,11 +32,16 @@ public class DSpaceEarliestDateResolver implements EarliestDateResolver { @Override public Date getEarliestDate(Context context) throws InvalidMetadataFieldException, SQLException { - String query = "SELECT MIN(text_value) as value FROM metadatavalue WHERE metadata_field_id = ?"; - MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService(); - MetadataValue minimum = metadataValueService.getMinimum(context, - fieldResolver.getFieldID(context, "dc.date.available")); + MetadataValue minimum = null; + try { + minimum = metadataValueService.getMinimum(context, + fieldResolver.getFieldID(context, "dc.date.available")); + } catch (NoResultException e) { + // This error only occurs if no metadataFields of this type exist (i.e. no minimum exists) + // It can be safely ignored in this scenario, as it implies the DSpace is empty. + } + if (null != minimum) { String str = minimum.getValue(); try { From f782a1ca3e45ce9fc5319ef9e36e7bfa878ab044 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 19 Feb 2019 17:19:47 +0000 Subject: [PATCH 22/52] OAI fixes: Update JTwig for compatibility with Spring Boot. Ensure static files are loaded properly --- dspace-oai/pom.xml | 38 +++++++--------- .../app/configuration/OAIWebConfig.java | 41 +++++++++--------- .../controller/DSpaceOAIDataProvider.java | 2 +- .../static/css/bootstrap-theme.css | 0 .../static/css/bootstrap-theme.min.css | 0 .../static/css/bootstrap.css | 0 .../static/css/bootstrap.min.css | 0 .../static/css/style.css | 0 .../fonts/glyphicons-halflings-regular.eot | Bin .../fonts/glyphicons-halflings-regular.svg | 0 .../fonts/glyphicons-halflings-regular.ttf | Bin .../fonts/glyphicons-halflings-regular.woff | Bin .../static/img/lyncode.png | Bin .../static/js/bootstrap.js | 0 .../static/js/bootstrap.min.js | 0 .../{webapp => resources}/static/js/jquery.js | 0 .../{webapp => resources}/static/lyncode.png | Bin .../{webapp => resources}/static/style.xsl | 0 .../templates}/index.twig.html | 30 ++++++++----- .../java/org/dspace/app/oai/OAIpmhIT.java | 2 +- 20 files changed, 56 insertions(+), 57 deletions(-) rename dspace-oai/src/main/{webapp => resources}/static/css/bootstrap-theme.css (100%) rename dspace-oai/src/main/{webapp => resources}/static/css/bootstrap-theme.min.css (100%) rename dspace-oai/src/main/{webapp => resources}/static/css/bootstrap.css (100%) rename dspace-oai/src/main/{webapp => resources}/static/css/bootstrap.min.css (100%) rename dspace-oai/src/main/{webapp => resources}/static/css/style.css (100%) rename dspace-oai/src/main/{webapp => resources}/static/fonts/glyphicons-halflings-regular.eot (100%) rename dspace-oai/src/main/{webapp => resources}/static/fonts/glyphicons-halflings-regular.svg (100%) rename dspace-oai/src/main/{webapp => resources}/static/fonts/glyphicons-halflings-regular.ttf (100%) rename dspace-oai/src/main/{webapp => resources}/static/fonts/glyphicons-halflings-regular.woff (100%) rename dspace-oai/src/main/{webapp => resources}/static/img/lyncode.png (100%) rename dspace-oai/src/main/{webapp => resources}/static/js/bootstrap.js (100%) rename dspace-oai/src/main/{webapp => resources}/static/js/bootstrap.min.js (100%) rename dspace-oai/src/main/{webapp => resources}/static/js/jquery.js (100%) rename dspace-oai/src/main/{webapp => resources}/static/lyncode.png (100%) rename dspace-oai/src/main/{webapp => resources}/static/style.xsl (100%) rename dspace-oai/src/main/{webapp/WEB-INF/views => resources/templates}/index.twig.html (74%) diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 716f222461..fd813b2e9c 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -17,7 +17,8 @@ ${basedir}/.. 3.2.10 - 2.0.1 + + 5.86.1.RELEASE @@ -28,7 +29,7 @@ - src/main/webapp/** + src/main/resources/** **/*.xsl @@ -116,34 +117,25 @@ - com.lyncode - jtwig-spring + org.jtwig + jtwig-spring-boot-starter ${jtwig.version} + - com.google.guava - guava + org.springframework.boot + spring-boot-starter-web + - org.javassist - javassist + org.ow2.asm + asm + - org.apache.commons - commons-lang3 + org.springframework.boot + spring-boot-configuration-processor - - javax.servlet - servlet-api - - - log4j - log4j - - - org.slf4j - slf4j-log4j12 - @@ -253,7 +245,7 @@ org.parboiled parboiled-core - 1.1.6 + 1.1.7 test diff --git a/dspace-oai/src/main/java/org/dspace/app/configuration/OAIWebConfig.java b/dspace-oai/src/main/java/org/dspace/app/configuration/OAIWebConfig.java index c4b4bc7ace..806ecf9d07 100644 --- a/dspace-oai/src/main/java/org/dspace/app/configuration/OAIWebConfig.java +++ b/dspace-oai/src/main/java/org/dspace/app/configuration/OAIWebConfig.java @@ -8,17 +8,16 @@ package org.dspace.app.configuration; import static java.lang.Integer.MAX_VALUE; -import com.lyncode.jtwig.mvc.JtwigViewResolver; import org.dspace.xoai.app.BasicConfiguration; import org.dspace.xoai.services.api.xoai.ItemRepositoryResolver; import org.dspace.xoai.services.impl.xoai.DSpaceItemRepositoryResolver; +import org.jtwig.spring.JtwigViewResolver; +import org.jtwig.spring.boot.config.JtwigViewResolverConfigurer; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.web.servlet.ViewResolver; -import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @@ -33,37 +32,39 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter * @author Tim Donohue */ @Configuration -// Import WebMVC configuration (and allow us to extend WebMvcConfigurerAdapter) -@EnableWebMvc // Import additional configuration and beans from BasicConfiguration @Import(BasicConfiguration.class) // Scan for controllers in this package @ComponentScan("org.dspace.xoai.controller") -public class OAIWebConfig extends WebMvcConfigurerAdapter { +public class OAIWebConfig extends WebMvcConfigurerAdapter implements JtwigViewResolverConfigurer { + + // Path where OAI is deployed. Defaults to "oai" + // NOTE: deployment on this path is handled by org.dspace.xoai.controller.DSpaceOAIDataProvider + @Value("${oai.path:oai}") + private String oaiPath; private static final String TWIG_HTML_EXTENSION = ".twig.html"; - private static final String VIEWS_LOCATION = "/WEB-INF/views/"; + private static final String VIEWS_LOCATION = "classpath:/templates/"; + /** + * Ensure all resources under src/main/resources/static/ directory are available + * off the /{oai.path}/static subpath + **/ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { - registry.addResourceHandler("/static/**") - .addResourceLocations("/static/") + registry.addResourceHandler("/" + oaiPath + "/static/**") + .addResourceLocations("classpath:/static/") .setCachePeriod(MAX_VALUE); } + /** + * Configure the Jtwig template engine for Spring Boot + * Ensures Jtwig looks for templates in proper location with proper extension + **/ @Override - public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { - configurer.enable(); - } - - @Bean - public ViewResolver viewResolver() { - JtwigViewResolver viewResolver = new JtwigViewResolver(); + public void configure(JtwigViewResolver viewResolver) { viewResolver.setPrefix(VIEWS_LOCATION); viewResolver.setSuffix(TWIG_HTML_EXTENSION); - viewResolver.setCached(false); - - return viewResolver; } @Bean diff --git a/dspace-oai/src/main/java/org/dspace/xoai/controller/DSpaceOAIDataProvider.java b/dspace-oai/src/main/java/org/dspace/xoai/controller/DSpaceOAIDataProvider.java index 6f33331a1d..212f1e3406 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/controller/DSpaceOAIDataProvider.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/controller/DSpaceOAIDataProvider.java @@ -72,7 +72,7 @@ public class DSpaceOAIDataProvider { private DSpaceResumptionTokenFormatter resumptionTokenFormat = new DSpaceResumptionTokenFormatter(); - @RequestMapping("/") + @RequestMapping({"", "/"}) public String indexAction(HttpServletResponse response, Model model) throws ServletException { try { XOAIManager manager = xoaiManagerResolver.getManager(); diff --git a/dspace-oai/src/main/webapp/static/css/bootstrap-theme.css b/dspace-oai/src/main/resources/static/css/bootstrap-theme.css similarity index 100% rename from dspace-oai/src/main/webapp/static/css/bootstrap-theme.css rename to dspace-oai/src/main/resources/static/css/bootstrap-theme.css diff --git a/dspace-oai/src/main/webapp/static/css/bootstrap-theme.min.css b/dspace-oai/src/main/resources/static/css/bootstrap-theme.min.css similarity index 100% rename from dspace-oai/src/main/webapp/static/css/bootstrap-theme.min.css rename to dspace-oai/src/main/resources/static/css/bootstrap-theme.min.css diff --git a/dspace-oai/src/main/webapp/static/css/bootstrap.css b/dspace-oai/src/main/resources/static/css/bootstrap.css similarity index 100% rename from dspace-oai/src/main/webapp/static/css/bootstrap.css rename to dspace-oai/src/main/resources/static/css/bootstrap.css diff --git a/dspace-oai/src/main/webapp/static/css/bootstrap.min.css b/dspace-oai/src/main/resources/static/css/bootstrap.min.css similarity index 100% rename from dspace-oai/src/main/webapp/static/css/bootstrap.min.css rename to dspace-oai/src/main/resources/static/css/bootstrap.min.css diff --git a/dspace-oai/src/main/webapp/static/css/style.css b/dspace-oai/src/main/resources/static/css/style.css similarity index 100% rename from dspace-oai/src/main/webapp/static/css/style.css rename to dspace-oai/src/main/resources/static/css/style.css diff --git a/dspace-oai/src/main/webapp/static/fonts/glyphicons-halflings-regular.eot b/dspace-oai/src/main/resources/static/fonts/glyphicons-halflings-regular.eot similarity index 100% rename from dspace-oai/src/main/webapp/static/fonts/glyphicons-halflings-regular.eot rename to dspace-oai/src/main/resources/static/fonts/glyphicons-halflings-regular.eot diff --git a/dspace-oai/src/main/webapp/static/fonts/glyphicons-halflings-regular.svg b/dspace-oai/src/main/resources/static/fonts/glyphicons-halflings-regular.svg similarity index 100% rename from dspace-oai/src/main/webapp/static/fonts/glyphicons-halflings-regular.svg rename to dspace-oai/src/main/resources/static/fonts/glyphicons-halflings-regular.svg diff --git a/dspace-oai/src/main/webapp/static/fonts/glyphicons-halflings-regular.ttf b/dspace-oai/src/main/resources/static/fonts/glyphicons-halflings-regular.ttf similarity index 100% rename from dspace-oai/src/main/webapp/static/fonts/glyphicons-halflings-regular.ttf rename to dspace-oai/src/main/resources/static/fonts/glyphicons-halflings-regular.ttf diff --git a/dspace-oai/src/main/webapp/static/fonts/glyphicons-halflings-regular.woff b/dspace-oai/src/main/resources/static/fonts/glyphicons-halflings-regular.woff similarity index 100% rename from dspace-oai/src/main/webapp/static/fonts/glyphicons-halflings-regular.woff rename to dspace-oai/src/main/resources/static/fonts/glyphicons-halflings-regular.woff diff --git a/dspace-oai/src/main/webapp/static/img/lyncode.png b/dspace-oai/src/main/resources/static/img/lyncode.png similarity index 100% rename from dspace-oai/src/main/webapp/static/img/lyncode.png rename to dspace-oai/src/main/resources/static/img/lyncode.png diff --git a/dspace-oai/src/main/webapp/static/js/bootstrap.js b/dspace-oai/src/main/resources/static/js/bootstrap.js similarity index 100% rename from dspace-oai/src/main/webapp/static/js/bootstrap.js rename to dspace-oai/src/main/resources/static/js/bootstrap.js diff --git a/dspace-oai/src/main/webapp/static/js/bootstrap.min.js b/dspace-oai/src/main/resources/static/js/bootstrap.min.js similarity index 100% rename from dspace-oai/src/main/webapp/static/js/bootstrap.min.js rename to dspace-oai/src/main/resources/static/js/bootstrap.min.js diff --git a/dspace-oai/src/main/webapp/static/js/jquery.js b/dspace-oai/src/main/resources/static/js/jquery.js similarity index 100% rename from dspace-oai/src/main/webapp/static/js/jquery.js rename to dspace-oai/src/main/resources/static/js/jquery.js diff --git a/dspace-oai/src/main/webapp/static/lyncode.png b/dspace-oai/src/main/resources/static/lyncode.png similarity index 100% rename from dspace-oai/src/main/webapp/static/lyncode.png rename to dspace-oai/src/main/resources/static/lyncode.png diff --git a/dspace-oai/src/main/webapp/static/style.xsl b/dspace-oai/src/main/resources/static/style.xsl similarity index 100% rename from dspace-oai/src/main/webapp/static/style.xsl rename to dspace-oai/src/main/resources/static/style.xsl diff --git a/dspace-oai/src/main/webapp/WEB-INF/views/index.twig.html b/dspace-oai/src/main/resources/templates/index.twig.html similarity index 74% rename from dspace-oai/src/main/webapp/WEB-INF/views/index.twig.html rename to dspace-oai/src/main/resources/templates/index.twig.html index fc530902a3..0045d8f5e5 100644 --- a/dspace-oai/src/main/webapp/WEB-INF/views/index.twig.html +++ b/dspace-oai/src/main/resources/templates/index.twig.html @@ -6,6 +6,12 @@ http://www.dspace.org/license/ +#} +{# + + DSpace OAI default index template. To override this template, place a customized version in + the [webapp]/WEB-INF/classes/templates/ folder, and reboot your servlet engine. + #} @@ -13,8 +19,8 @@ DSpace OAI-PMH Data Provider - - + + @@ -24,9 +30,9 @@ - - - + + + @@ -56,11 +62,11 @@

@@ -73,11 +79,11 @@

Design by Lyncode

- Lyncode + Lyncode

- \ No newline at end of file + diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/oai/OAIpmhIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/oai/OAIpmhIT.java index 4c343bf518..1ddc5eb1c0 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/oai/OAIpmhIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/oai/OAIpmhIT.java @@ -59,7 +59,7 @@ public class OAIpmhIT extends AbstractControllerIntegrationTest { // Attempt to call the root endpoint getClient().perform(get(ROOT_PATH)) // Expect a 400 response code (OAI requires a context) - .andExpect(status().isOk()) + .andExpect(status().isBadRequest()) ; } From 805e35743b1befb2b31f91470aed50a52bc917e1 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 22 Feb 2019 15:19:09 +0000 Subject: [PATCH 23/52] Enhance inline comments in application.properties --- .../src/main/resources/application.properties | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/dspace-spring-rest/src/main/resources/application.properties b/dspace-spring-rest/src/main/resources/application.properties index fd395f38f5..7a5e5a0193 100644 --- a/dspace-spring-rest/src/main/resources/application.properties +++ b/dspace-spring-rest/src/main/resources/application.properties @@ -7,10 +7,19 @@ # # -# Spring Boot application.properties -# Docs (including info on how to override these default settings) +# Spring Boot's application.properties +# +# This properties file is used by Spring Boot to initialize its ApplicationContext and configure +# default Spring beans. It also uses the "dspace.dir" custom setting to locate your DSpace installation, +# and load all DSpace services and configurations. +# +# WARNING: Because this properties file initializes Spring Boot, it loads *before* any DSpace specific +# configurations/settings. Therefore settings in this file CANNOT depend on any DSpace configurations. +# The *only* DSpace configuration allowed in this file is "dspace.dir", which is documented below. +# +# Docs (including info on how to override these default settings) # http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html -# For common settings see: +# For common settings see: # http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html # @@ -20,6 +29,8 @@ # DSpace home/installation directory # REQUIRED to be specified in this application.properties file, as it is used to locate and initialize # the DSpace Kernel and all Services (including configuration). See org.dspace.app.rest.Application.getDSpaceHome() +# NOTE: this configuration is filled out by Apache Ant during the DSpace install/update process. It does NOT +# interact with or read its configuration from dspace.cfg. dspace.dir=${dspace.dir} ######################## @@ -71,8 +82,8 @@ server.error.include-stacktrace = always # # DISABLE a few autoconfiguration scripts, as DSpace initializes/configures these already # * DataSourceAutoConfiguration (DB connection / datasource) +# * HibernateJpaAutoConfiguration (Hibernate ORM) # * FlywayAutoConfiguration (Flyway migrations) -# * HibernateJpaAutoConfiguration (Hibernate) # * SolrAutoConfiguration (Solr) # # TODO: At some point we may want to investigate whether we can re-enable these and remove the custom DSpace init code From 379968d12c04e76c1ed5158c99ac3fbe98fa8094 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 25 Feb 2019 22:55:52 +0000 Subject: [PATCH 24/52] Migrate existing OAI IntegrationTests to OAIpmhIT class in Spring Boot. Disable existing ITs (will be removed) --- .../tests/integration/xoai/IdentifyTest.java | 2 + .../tests/integration/xoai/ListSetsTest.java | 2 + .../integration/xoai/OAIContextTest.java | 2 + .../java/org/dspace/app/oai/OAIpmhIT.java | 205 +++++++++++++++++- .../AbstractIntegrationTestWithDatabase.java | 3 + 5 files changed, 207 insertions(+), 7 deletions(-) diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/IdentifyTest.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/IdentifyTest.java index f49e4e07e2..173a118f73 100644 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/IdentifyTest.java +++ b/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/IdentifyTest.java @@ -15,8 +15,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.util.Date; +import org.junit.Ignore; import org.junit.Test; +@Ignore public class IdentifyTest extends AbstractDSpaceTest { public static final Date EARLIEST_DATE = new Date(); diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/ListSetsTest.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/ListSetsTest.java index 282d6a6c9d..d0bcd75c03 100644 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/ListSetsTest.java +++ b/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/ListSetsTest.java @@ -14,8 +14,10 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import org.junit.Ignore; import org.junit.Test; +@Ignore public class ListSetsTest extends AbstractDSpaceTest { @Test public void listSetsWithLessSetsThenMaxSetsPerPage() throws Exception { diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/OAIContextTest.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/OAIContextTest.java index ef875e1896..c96445ac0d 100644 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/OAIContextTest.java +++ b/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/OAIContextTest.java @@ -12,8 +12,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultHandlers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import org.junit.Ignore; import org.junit.Test; +@Ignore public class OAIContextTest extends AbstractDSpaceTest { public static final String ROOT_URL = "/"; diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/oai/OAIpmhIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/oai/OAIpmhIT.java index 1ddc5eb1c0..3869baefab 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/oai/OAIpmhIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/oai/OAIpmhIT.java @@ -8,18 +8,42 @@ package org.dspace.app.oai; +import static org.hamcrest.Matchers.startsWith; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doReturn; +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.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.xpath; +import java.util.Date; + +import com.lyncode.xoai.dataprovider.core.XOAIManager; +import com.lyncode.xoai.dataprovider.exceptions.ConfigurationException; +import com.lyncode.xoai.dataprovider.services.api.ResourceResolver; +import com.lyncode.xoai.dataprovider.services.impl.BaseDateProvider; +import com.lyncode.xoai.dataprovider.xml.xoaiconfig.Configuration; +import com.lyncode.xoai.dataprovider.xml.xoaiconfig.ContextConfiguration; + +import org.dspace.app.rest.builder.CollectionBuilder; +import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.content.Community; import org.dspace.services.ConfigurationService; +import org.dspace.xoai.services.api.EarliestDateResolver; +import org.dspace.xoai.services.api.cache.XOAICacheService; +import org.dspace.xoai.services.api.config.XOAIManagerResolver; +import org.dspace.xoai.services.api.xoai.DSpaceFilterResolver; + import org.junit.Assume; import org.junit.Before; -//import org.junit.Ignore; import org.junit.Test; +import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.test.context.TestPropertySource; /** @@ -40,7 +64,30 @@ public class OAIpmhIT extends AbstractControllerIntegrationTest { // All OAI-PMH paths that we test against private final String ROOT_PATH = "/oai"; - private final String ROOT_REQUEST = "/oai/request"; + private final String DEFAULT_CONTEXT_PATH = "request"; + private final String DEFAULT_CONTEXT = ROOT_PATH + "/" + DEFAULT_CONTEXT_PATH; + + // Mock to ensure XOAI caching is disabled for all tests (see @Before method) + @MockBean + private XOAICacheService xoaiCacheService; + + // Mock for date-based testing below + @Mock + private EarliestDateResolver earliestDateResolver; + // XOAI's BaseDateProvider (used for date-based testing below) + private static BaseDateProvider baseDateProvider = new BaseDateProvider(); + + // Spy on the current XOAIManagerResolver, to allow us to change behavior of XOAIManager in tests + // See also: createMockXOAIManager() method + @SpyBean + private XOAIManagerResolver xoaiManagerResolver; + + // Beans required by createMockXOAIManager() + @Autowired + private ResourceResolver resourceResolver; + @Autowired + private DSpaceFilterResolver filterResolver; + @Before public void onlyRunIfConfigExists() { @@ -52,22 +99,55 @@ public class OAIpmhIT extends AbstractControllerIntegrationTest { } catch (ClassNotFoundException ce) { Assume.assumeNoException(ce); } + + // Disable XOAI Caching for ALL tests + when(xoaiCacheService.isActive()).thenReturn(false); + when(xoaiCacheService.hasCache(anyString())).thenReturn(false); } @Test - public void oaiRootTest() throws Exception { + public void requestToRootShouldGiveListOfContextsWithBadRequestError() throws Exception { // Attempt to call the root endpoint getClient().perform(get(ROOT_PATH)) - // Expect a 400 response code (OAI requires a context) + // Expect a 400 response code (OAI requires a context) .andExpect(status().isBadRequest()) + // Expect that a list of valid contexts is returned + .andExpect(model().attributeExists("contexts")) ; } + @Test + public void requestForUnknownContextShouldGiveListOfContextsWithBadRequestError() throws Exception { + // Attempt to call an nonexistent OAI-PMH context + getClient().perform(get(ROOT_PATH + "/nonexistentContext")) + // Expect a 400 response code (OAI requires a context) + .andExpect(status().isBadRequest()) + // Expect that a list of valid contexts is returned + .andExpect(model().attributeExists("contexts")) + ; + } @Test - public void oaiRootIdentifyTest() throws Exception { + public void requestForIdentifyWithoutRequiredConfigShouldFail() throws Exception { + // Clear out the required "mail.admin" configuration + configurationService.setProperty("mail.admin", null); + // Attempt to make an Identify request to root context - getClient().perform(get(ROOT_REQUEST).param("verb", "Identify")) + getClient().perform(get(DEFAULT_CONTEXT).param("verb", "Identify")) + // Expect a 500 response code (mail.admin MUST be set) + .andExpect(status().isInternalServerError()) + ; + } + + @Test + public void requestForIdentifyShouldReturnTheConfiguredValues() throws Exception { + + // Mock EarliestDateResolver to return now for getEarliestDate() + Date now = new Date(); + when(earliestDateResolver.getEarliestDate(context)).thenReturn(now); + + // Attempt to make an Identify request to root context + getClient().perform(get(DEFAULT_CONTEXT).param("verb", "Identify")) // Expect a 200 response code .andExpect(status().isOk()) // Expect the content type to be "text/xml" @@ -79,8 +159,119 @@ public class OAIpmhIT extends AbstractControllerIntegrationTest { // Expect repositoryName to be the same as "dspace.name" config .andExpect(xpath("OAI-PMH/Identify/repositoryName") .string(configurationService.getProperty("dspace.name"))) + // Expect adminEmail to be the same as "mail.admin" config + .andExpect(xpath("OAI-PMH/Identify/adminEmail") + .string(configurationService.getProperty("mail.admin"))) + // Expect earliestDatestamp to be "now", i.e. current date, (as mocked above) + .andExpect(xpath("OAI-PMH/Identify/earliestDatestamp") + .string(baseDateProvider.format(now))) ; } -} + @Test + public void listSetsWithLessSetsThenMaxSetsPerPage() throws Exception { + //Turn off the authorization system, otherwise we can't make the objects + context.turnOffAuthorisationSystem(); + // Create a Community & a Collection + Community parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Child Collection") + .build(); + context.restoreAuthSystemState(); + + // Call ListSets verb, and verify both Collection & Community are listed as sets + getClient().perform(get(DEFAULT_CONTEXT).param("verb", "ListSets")) + // Expect 200 response, with valid response date and verb=ListSets + .andExpect(status().isOk()) + .andExpect(xpath("OAI-PMH/responseDate").exists()) + .andExpect(xpath("OAI-PMH/request/@verb").string("ListSets")) + // Expect two Sets to be returned + .andExpect(xpath("//set").nodeCount(2)) + // First setSpec should start with "com_" (Community) + .andExpect(xpath("(//set/setSpec)[1]").string(startsWith("com_"))) + // First set name should be Community name + .andExpect(xpath("(//set/setName)[1]").string("Parent Community")) + // Second setSpec should start with "col_" (Collection) + .andExpect(xpath("(//set/setSpec)[2]").string(startsWith("col_"))) + // Second set name should be Collection name + .andExpect(xpath("(//set/setName)[2]").string("Child Collection")) + // No resumption token should be returned + .andExpect(xpath("//resumptionToken").doesNotExist()) + ; + } + + @Test + public void listSetsWithMoreSetsThenMaxSetsPerPage() throws Exception { + //Turn off the authorization system, otherwise we can't make the objects + context.turnOffAuthorisationSystem(); + + // Create 3 Communities (1 as a subcommunity) & 2 Collections + Community firstCommunity = CommunityBuilder.createCommunity(context) + .withName("First Community") + .build(); + Community secondCommunity = CommunityBuilder.createSubCommunity(context, firstCommunity) + .withName("Second Community") + .build(); + CommunityBuilder.createCommunity(context) + .withName("Third Community") + .build(); + CollectionBuilder.createCollection(context, firstCommunity) + .withName("First Collection") + .build(); + CollectionBuilder.createCollection(context, secondCommunity) + .withName("Second Collection") + .build(); + context.restoreAuthSystemState(); + + + // Create a custom XOAI configuration, with maxListSetsSize = 3 for DEFAULT_CONTEXT requests + // (This limits the number of sets returned in a single response) + Configuration xoaiConfig = + new Configuration().withMaxListSetsSize(3) + .withContextConfigurations(new ContextConfiguration(DEFAULT_CONTEXT_PATH)); + // When xoaiManagerResolver.getManager() is called, return a MockXOAIManager based on the above configuration + doReturn(createMockXOAIManager(xoaiConfig)).when(xoaiManagerResolver).getManager(); + + // Call ListSets verb, and verify all 5 Collections/Communities are listed as sets + getClient().perform(get(DEFAULT_CONTEXT).param("verb", "ListSets")) + // Expect 200 response, with valid response date and verb=ListSets + .andExpect(status().isOk()) + .andExpect(xpath("OAI-PMH/responseDate").exists()) + .andExpect(xpath("OAI-PMH/request/@verb").string("ListSets")) + // Expect ONLY 3 (of 5) Sets to be returned + .andExpect(xpath("//set").nodeCount(3)) + // Expect resumption token to exist and be equal to "////3" + .andExpect(xpath("//resumptionToken").string("////3")) + // Expect resumption token to have completeListSize=5 + .andExpect(xpath("//resumptionToken/@completeListSize").number(Double.valueOf(5))) + ; + + // Call ListSets verb, and verify all 5 Collections/Communities are listed as sets + getClient().perform(get(DEFAULT_CONTEXT).param("verb", "ListSets")) + // Expect 200 response, with valid response date and verb=ListSets + .andExpect(status().isOk()) + .andExpect(xpath("OAI-PMH/responseDate").exists()) + .andExpect(xpath("OAI-PMH/request/@verb").string("ListSets")) + // Expect ONLY 3 (of 5) Sets to be returned + .andExpect(xpath("//set").nodeCount(3)) + // Expect resumption token to exist and be equal to "////3" + .andExpect(xpath("//resumptionToken").string("////3")) + // Expect resumption token to have completeListSize=5 + .andExpect(xpath("//resumptionToken/@completeListSize").number(Double.valueOf(5))) + ; + } + + /** + * Create a fake/mock XOAIManager class based on the given xoaiConfig. May be used by above tests + * to provide custom configurations to XOAI (overriding defaults in xoai.xml) + * @param xoaiConfig XOAI Configuration + * @return new XOAIManager initialized with the given Configuration + * @throws ConfigurationException + */ + private XOAIManager createMockXOAIManager(Configuration xoaiConfig) throws ConfigurationException { + return new XOAIManager(filterResolver, resourceResolver, xoaiConfig); + } +} diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractIntegrationTestWithDatabase.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractIntegrationTestWithDatabase.java index 2aae82f96c..b655cb8e23 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractIntegrationTestWithDatabase.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractIntegrationTestWithDatabase.java @@ -184,6 +184,9 @@ public class AbstractIntegrationTestWithDatabase extends AbstractDSpaceIntegrati .getServiceByName(SearchService.class.getName(), MockSolrServiceImpl.class); searchService.reset(); + // Reload our ConfigurationService (to reset configs to defaults again) + DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); + // NOTE: we explicitly do NOT destroy our default eperson & admin as they // are cached and reused for all tests. This speeds up all tests. } catch (Exception e) { From 8c05914c04fa3ea55aeb479d732cafea57726cb5 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 27 Feb 2019 17:11:51 +0000 Subject: [PATCH 25/52] Fix CommunityRestRepositoryIT tests that were not cleaning up after themselves --- .../app/rest/CommunityRestRepositoryIT.java | 117 +++++++++++------- .../app/rest/builder/CommunityBuilder.java | 23 ++++ 2 files changed, 96 insertions(+), 44 deletions(-) diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java index 3b3c12c720..bbbfe44a30 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.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.dspace.app.rest.matcher.MetadataMatcher.matchMetadata; import static org.hamcrest.Matchers.empty; @@ -21,6 +22,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -37,6 +39,7 @@ import org.dspace.app.rest.test.MetadataPatchSuite; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Collection; import org.dspace.content.Community; +import org.dspace.content.service.CommunityService; import org.dspace.core.Constants; import org.dspace.eperson.EPerson; import org.hamcrest.Matchers; @@ -44,18 +47,22 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; +/** + * Integration Tests against the /api/core/communities endpoint (including any subpaths) + */ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired CommunityConverter communityConverter; + @Autowired + CommunityService communityService; + @Autowired AuthorizeService authorizeService; @Test public void createTest() throws Exception { - context.turnOffAuthorisationSystem(); - ObjectMapper mapper = new ObjectMapper(); CommunityRest comm = new CommunityRest(); // We send a name but the created community should set this to the title @@ -86,41 +93,54 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest comm.setMetadata(metadataRest); String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(post("/api/core/communities") + + // Capture the UUID of the created Community (see andDo() below) + AtomicReference idRef = new AtomicReference(); + try { + getClient(authToken).perform(post("/api/core/communities") .content(mapper.writeValueAsBytes(comm)) .contentType(contentType)) - .andExpect(status().isCreated()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.id", not(empty())), - hasJsonPath("$.uuid", not(empty())), - hasJsonPath("$.name", is("Title Text")), - hasJsonPath("$.handle", not(empty())), - hasJsonPath("$.type", is("community")), - hasJsonPath("$._links.collections.href", not(empty())), - hasJsonPath("$._links.logo.href", not(empty())), - hasJsonPath("$._links.subcommunities.href", not(empty())), - hasJsonPath("$._links.self.href", not(empty())), - hasJsonPath("$.metadata", Matchers.allOf( - matchMetadata("dc.description", "

Some cool HTML code here

"), - matchMetadata("dc.description.abstract", + .andExpect(status().isCreated()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.id", not(empty())), + hasJsonPath("$.uuid", not(empty())), + hasJsonPath("$.name", is("Title Text")), + hasJsonPath("$.handle", not(empty())), + hasJsonPath("$.type", is("community")), + hasJsonPath("$._links.collections.href", not(empty())), + hasJsonPath("$._links.logo.href", not(empty())), + hasJsonPath("$._links.subcommunities.href", not(empty())), + hasJsonPath("$._links.self.href", not(empty())), + hasJsonPath("$.metadata", Matchers.allOf( + matchMetadata("dc.description", "

Some cool HTML code here

"), + matchMetadata("dc.description.abstract", "Sample top-level community created via the REST API"), - matchMetadata("dc.description.tableofcontents", "

HTML News

"), - matchMetadata("dc.rights", "Custom Copyright Text"), - matchMetadata("dc.title", "Title Text") - ))))); + matchMetadata("dc.description.tableofcontents", "

HTML News

"), + matchMetadata("dc.rights", "Custom Copyright Text"), + matchMetadata("dc.title", "Title Text") + ) + ) + ))) + // capture "id" returned in JSON response + .andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id")))); + } finally { + // Delete the created community (cleanup after ourselves!) + CommunityBuilder.deleteCommunity(idRef.get()); + } } + @Test public void createWithParentTest() throws Exception { - context.turnOffAuthorisationSystem(); //We turn off the authorization system in order to create the structure as defined below context.turnOffAuthorisationSystem(); - //** GIVEN ** - //1. A community-collection structure with one parent community with sub-community and one collection. + // Create a parent community to POST a new sub-community to parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") .build(); + context.restoreAuthSystemState(); ObjectMapper mapper = new ObjectMapper(); CommunityRest comm = new CommunityRest(); @@ -140,23 +160,26 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest new MetadataValueRest("Title Text"))); String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(post("/api/core/communities") + // Capture the UUID of the created Community (see andDo() below) + AtomicReference idRef = new AtomicReference(); + try { + getClient(authToken).perform(post("/api/core/communities") .content(mapper.writeValueAsBytes(comm)) .param("parent", parentCommunity.getID().toString()) .contentType(contentType)) - .andExpect(status().isCreated()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.id", not(empty())), - hasJsonPath("$.uuid", not(empty())), - hasJsonPath("$.name", is("Title Text")), - hasJsonPath("$.handle", not(empty())), - hasJsonPath("$.type", is("community")), - hasJsonPath("$._links.collections.href", not(empty())), - hasJsonPath("$._links.logo.href", not(empty())), - hasJsonPath("$._links.subcommunities.href", not(empty())), - hasJsonPath("$._links.self.href", not(empty())), - hasJsonPath("$.metadata", Matchers.allOf( + .andExpect(status().isCreated()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.id", not(empty())), + hasJsonPath("$.uuid", not(empty())), + hasJsonPath("$.name", is("Title Text")), + hasJsonPath("$.handle", not(empty())), + hasJsonPath("$.type", is("community")), + hasJsonPath("$._links.collections.href", not(empty())), + hasJsonPath("$._links.logo.href", not(empty())), + hasJsonPath("$._links.subcommunities.href", not(empty())), + hasJsonPath("$._links.self.href", not(empty())), + hasJsonPath("$.metadata", Matchers.allOf( MetadataMatcher.matchMetadata("dc.description", "

Some cool HTML code here

"), MetadataMatcher.matchMetadata("dc.description.abstract", @@ -167,13 +190,18 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest "Custom Copyright Text"), MetadataMatcher.matchMetadata("dc.title", "Title Text") - ))))); - - - + ) + ) + ))) + // capture "id" returned in JSON response + .andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id")))); + } finally { + // Delete the created community (cleanup after ourselves!) + CommunityBuilder.deleteCommunity(idRef.get()); + } } - @Test public void createUnauthorizedTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -854,6 +882,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest } + @Test public void patchCommunityMetadataAuthorized() throws Exception { runPatchMetadataTests(admin, 200); } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/CommunityBuilder.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/CommunityBuilder.java index 420e614e0b..befa1daad3 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/CommunityBuilder.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/CommunityBuilder.java @@ -10,6 +10,7 @@ package org.dspace.app.rest.builder; 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.CharEncoding; @@ -92,4 +93,26 @@ public class CommunityBuilder extends AbstractDSpaceObjectBuilder { protected DSpaceObjectService getService() { return communityService; } + + /** + * Delete the Test Community referred to by the given UUID + * @param uuid UUID of Test Community to delete + * @throws SQLException + * @throws IOException + */ + public static void deleteCommunity(UUID uuid) throws SQLException, IOException { + try (Context c = new Context()) { + c.turnOffAuthorisationSystem(); + Community community = communityService.find(c, uuid); + if (community != null) { + try { + communityService.delete(c, community); + } catch (AuthorizeException e) { + // cannot occur, just wrap it to make the compiler happy + throw new RuntimeException(e.getMessage(), e); + } + } + c.complete(); + } + } } From 20e4da56d322f8d8d9ce9edaa10d3c67a926d2c5 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 1 Mar 2019 21:51:03 +0000 Subject: [PATCH 26/52] Remove OAI Integration Tests, framework & stubs. These ITs were moved to Spring Boot in previous commit. --- .../tests/DSpaceBasicTestConfiguration.java | 10 - .../xoai/tests/DSpaceTestConfiguration.java | 123 ------------ .../tests/helpers/HttpRequestBuilder.java | 37 ---- .../xoai/tests/helpers/SyntacticSugar.java | 28 --- .../helpers/stubs/ItemRepositoryBuilder.java | 183 ----------------- .../stubs/StubbedConfigurationService.java | 63 ------ .../stubs/StubbedEarliestDateResolver.java | 29 --- .../stubs/StubbedResourceResolver.java | 44 ----- .../helpers/stubs/StubbedSetRepository.java | 74 ------- .../stubs/StubbedXOAIManagerResolver.java | 39 ---- .../integration/xoai/AbstractDSpaceTest.java | 186 ------------------ .../tests/integration/xoai/IdentifyTest.java | 52 ----- .../tests/integration/xoai/ListSetsTest.java | 67 ------- .../integration/xoai/OAIContextTest.java | 37 ---- 14 files changed, 972 deletions(-) delete mode 100644 dspace-oai/src/test/java/org/dspace/xoai/tests/DSpaceTestConfiguration.java delete mode 100644 dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/HttpRequestBuilder.java delete mode 100644 dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/SyntacticSugar.java delete mode 100644 dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/ItemRepositoryBuilder.java delete mode 100644 dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedConfigurationService.java delete mode 100644 dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedEarliestDateResolver.java delete mode 100644 dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedResourceResolver.java delete mode 100644 dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedSetRepository.java delete mode 100644 dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedXOAIManagerResolver.java delete mode 100644 dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/AbstractDSpaceTest.java delete mode 100644 dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/IdentifyTest.java delete mode 100644 dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/ListSetsTest.java delete mode 100644 dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/OAIContextTest.java diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/DSpaceBasicTestConfiguration.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/DSpaceBasicTestConfiguration.java index e50f2f969d..7b81468a8e 100644 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/DSpaceBasicTestConfiguration.java +++ b/dspace-oai/src/test/java/org/dspace/xoai/tests/DSpaceBasicTestConfiguration.java @@ -10,11 +10,9 @@ package org.dspace.xoai.tests; import static org.mockito.Mockito.mock; import org.dspace.xoai.services.api.FieldResolver; -import org.dspace.xoai.services.api.config.ConfigurationService; import org.dspace.xoai.services.api.context.ContextService; import org.dspace.xoai.services.api.xoai.DSpaceFilterResolver; import org.dspace.xoai.services.impl.xoai.BaseDSpaceFilterResolver; -import org.dspace.xoai.tests.helpers.stubs.StubbedConfigurationService; import org.dspace.xoai.tests.helpers.stubs.StubbedFieldResolver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -29,14 +27,6 @@ public class DSpaceBasicTestConfiguration { return new BaseDSpaceFilterResolver(); } - - private StubbedConfigurationService configurationService = new StubbedConfigurationService(); - - @Bean - public ConfigurationService configurationService() { - return configurationService; - } - @Bean public ContextService contextService() { return mock(ContextService.class); diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/DSpaceTestConfiguration.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/DSpaceTestConfiguration.java deleted file mode 100644 index bc526cdf53..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/DSpaceTestConfiguration.java +++ /dev/null @@ -1,123 +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.xoai.tests; - -import com.lyncode.xoai.dataprovider.services.api.ItemRepository; -import com.lyncode.xoai.dataprovider.services.api.ResourceResolver; -import com.lyncode.xoai.dataprovider.services.api.SetRepository; -import org.dspace.core.Context; -import org.dspace.xoai.services.api.EarliestDateResolver; -import org.dspace.xoai.services.api.cache.XOAICacheService; -import org.dspace.xoai.services.api.config.XOAIManagerResolver; -import org.dspace.xoai.services.api.context.ContextService; -import org.dspace.xoai.services.api.context.ContextServiceException; -import org.dspace.xoai.services.api.xoai.IdentifyResolver; -import org.dspace.xoai.services.api.xoai.ItemRepositoryResolver; -import org.dspace.xoai.services.api.xoai.SetRepositoryResolver; -import org.dspace.xoai.services.impl.cache.DSpaceEmptyCacheService; -import org.dspace.xoai.services.impl.xoai.DSpaceIdentifyResolver; -import org.dspace.xoai.tests.helpers.stubs.StubbedEarliestDateResolver; -import org.dspace.xoai.tests.helpers.stubs.StubbedResourceResolver; -import org.dspace.xoai.tests.helpers.stubs.StubbedSetRepository; -import org.dspace.xoai.tests.helpers.stubs.StubbedXOAIManagerResolver; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.web.servlet.ViewResolver; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; -import org.springframework.web.servlet.view.InternalResourceViewResolver; - -@Import(DSpaceBasicTestConfiguration.class) -@Configuration -@EnableWebMvc -public class DSpaceTestConfiguration extends WebMvcConfigurerAdapter { - private static final String TWIG_HTML_EXTENSION = ".twig.html"; - private static final String VIEWS_LOCATION = "/WEB-INF/views/"; - - - @Bean - public ContextService contextService() { - return new ContextService() { - @Override - public Context getContext() throws ContextServiceException { - return null; - } - }; - } - - private StubbedResourceResolver resourceResolver = new StubbedResourceResolver(); - - @Bean - public ResourceResolver resourceResolver() { - return resourceResolver; - } - - @Bean - public ViewResolver viewResolver() { - InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); - viewResolver.setPrefix(VIEWS_LOCATION); - viewResolver.setSuffix(TWIG_HTML_EXTENSION); -// viewResolver.setCached(true); -// viewResolver.setTheme(null); - - return viewResolver; - } - - - @Bean - public XOAIManagerResolver xoaiManagerResolver() { - return new StubbedXOAIManagerResolver(); - } - - @Bean - public XOAICacheService xoaiCacheService() { - return new DSpaceEmptyCacheService(); - } - - private StubbedSetRepository setRepository = new StubbedSetRepository(); - - @Bean - StubbedSetRepository setRepository() { - return setRepository; - } - - @Bean - public ItemRepositoryResolver itemRepositoryResolver() { - return new ItemRepositoryResolver() { - @Override - public ItemRepository getItemRepository() throws ContextServiceException { - try { - return null; - } catch (Exception e) { - throw new ContextServiceException(e); - } - } - }; - } - - @Bean - public SetRepositoryResolver setRepositoryResolver() { - return new SetRepositoryResolver() { - @Override - public SetRepository getSetRepository() throws ContextServiceException { - return setRepository; - } - }; - } - - @Bean - public IdentifyResolver identifyResolver() { - return new DSpaceIdentifyResolver(); - } - - @Bean - public EarliestDateResolver earliestDateResolver() { - return new StubbedEarliestDateResolver(); - } -} diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/HttpRequestBuilder.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/HttpRequestBuilder.java deleted file mode 100644 index 153b05576e..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/HttpRequestBuilder.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.xoai.tests.helpers; - -import java.net.URI; -import java.net.URISyntaxException; -import javax.servlet.http.HttpServletRequest; - -import org.springframework.mock.web.MockHttpServletRequest; - -public class HttpRequestBuilder { - private MockHttpServletRequest request = new MockHttpServletRequest(); - - public HttpRequestBuilder withUrl(String url) { - try { - URI uri = new URI(url); - } catch (URISyntaxException e) { - // ASD - } - return this; - } - - public HttpRequestBuilder usingGetMethod() { - request.setMethod("GET"); - return this; - } - - public HttpServletRequest build() { - - return request; - } -} diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/SyntacticSugar.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/SyntacticSugar.java deleted file mode 100644 index a6cd3a3249..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/SyntacticSugar.java +++ /dev/null @@ -1,28 +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.xoai.tests.helpers; - -public class SyntacticSugar { - - /** - * Default constructor - */ - private SyntacticSugar() { } - - public static T given(T elem) { - return elem; - } - - public static T the(T elem) { - return elem; - } - - public static T and(T elem) { - return elem; - } -} diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/ItemRepositoryBuilder.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/ItemRepositoryBuilder.java deleted file mode 100644 index c01580e990..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/ItemRepositoryBuilder.java +++ /dev/null @@ -1,183 +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.xoai.tests.helpers.stubs; - -import static com.lyncode.xoai.dataprovider.core.Granularity.Second; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.sql.SQLException; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import javax.xml.stream.XMLStreamException; - -import com.lyncode.xoai.builders.dataprovider.ElementBuilder; -import com.lyncode.xoai.builders.dataprovider.MetadataBuilder; -import com.lyncode.xoai.dataprovider.exceptions.MetadataBindException; -import com.lyncode.xoai.dataprovider.exceptions.WritingXmlException; -import com.lyncode.xoai.dataprovider.xml.XmlOutputContext; -import com.lyncode.xoai.dataprovider.xml.xoai.Metadata; -import org.apache.solr.client.solrj.SolrClient; -import org.apache.solr.client.solrj.SolrServerException; -import org.apache.solr.common.SolrInputDocument; - -public class ItemRepositoryBuilder { - private final SolrClient solrServer; - - public ItemRepositoryBuilder(SolrClient solrServer) { - this.solrServer = solrServer; - } - - public ItemRepositoryBuilder withItem(DSpaceItemBuilder builder) { - try { - solrServer.add(index(builder)); - solrServer.commit(); - } catch (MetadataBindException | WritingXmlException | IOException - | SQLException | ParseException | XMLStreamException - | SolrServerException e) { - throw new RuntimeException(e); - } - return this; - } - - - private SolrInputDocument index(DSpaceItemBuilder item) - throws SQLException, MetadataBindException, ParseException, XMLStreamException, WritingXmlException { - SolrInputDocument doc = new SolrInputDocument(); - - doc.addField("item.id", item.getId()); - doc.addField("item.public", item.isPublic()); - doc.addField("item.lastmodified", item.getLastModifiedDate()); - doc.addField("item.submitter", item.getSubmitter()); - doc.addField("item.handle", item.getHandle()); - doc.addField("item.deleted", item.isDeleted()); - - for (String col : item.getCollections()) { - doc.addField("item.collections", col); - } - - for (String col : item.getCommunities()) { - doc.addField("item.communities", col); - } - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - XmlOutputContext context = XmlOutputContext.emptyContext(out, Second); - item.getMetadata().write(context); - context.getWriter().flush(); - context.getWriter().close(); - doc.addField("item.compile", out.toString()); - - return doc; - } - - public static class DSpaceItemBuilder { - private final List collections = new ArrayList<>(); - private final List communities = new ArrayList<>(); - private final MetadataBuilder metadataBuilder = new MetadataBuilder(); - private String handle; - private int id; - private String submitter; - private Date lastModifiedDate; - private boolean deleted; - private boolean aPublic = true; - - - public DSpaceItemBuilder withLastModifiedDate(Date lastModifiedDate) { - this.lastModifiedDate = lastModifiedDate; - return this; - } - - public DSpaceItemBuilder withCollection(String colName) { - collections.add(colName); - return this; - } - - public DSpaceItemBuilder withCommunity(String comName) { - communities.add(comName); - return this; - } - - public DSpaceItemBuilder whichSsPublic() { - aPublic = true; - return this; - } - - public DSpaceItemBuilder whichSsPrivate() { - aPublic = false; - return this; - } - - public DSpaceItemBuilder whichIsDeleted() { - this.deleted = true; - return this; - } - - public DSpaceItemBuilder whichIsNotDeleted() { - this.deleted = false; - return this; - } - - public DSpaceItemBuilder withMetadata(String schema, String element, String value) { - metadataBuilder.withElement(new ElementBuilder().withName(schema).withField(element, value).build()); - return this; - } - - public String getHandle() { - return handle; - } - - public DSpaceItemBuilder withHandle(String handle) { - this.handle = handle; - return this; - } - - public DSpaceItemBuilder withSubmitter(String submitter) { - this.submitter = submitter; - return this; - } - - public DSpaceItemBuilder withId(int id) { - this.id = id; - return this; - } - - public int getId() { - return id; - } - - public String getSubmitter() { - return submitter; - } - - public Date getLastModifiedDate() { - return lastModifiedDate; - } - - public List getCollections() { - return collections; - } - - public List getCommunities() { - return communities; - } - - public Metadata getMetadata() { - return metadataBuilder.build(); - } - - public boolean isDeleted() { - return deleted; - } - - public boolean isPublic() { - return aPublic; - } - } -} diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedConfigurationService.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedConfigurationService.java deleted file mode 100644 index c6e7a3cc99..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedConfigurationService.java +++ /dev/null @@ -1,63 +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.xoai.tests.helpers.stubs; - -import java.util.HashMap; -import java.util.Map; - -import org.dspace.xoai.services.api.config.ConfigurationService; - -public class StubbedConfigurationService implements ConfigurationService { - private Map values = new HashMap(); - - - public StubbedConfigurationService hasBooleanProperty(String key, boolean value) { - values.put(key, value); - return this; - } - - public StubbedConfigurationService hasBooleanProperty(String module, String key, boolean value) { - values.put(module + "." + key, value); - return this; - } - - public StubbedConfigurationService hasProperty(String key, String value) { - values.put(key, value); - return this; - } - - public StubbedConfigurationService withoutProperty(String key) { - values.remove(key); - return this; - } - - public StubbedConfigurationService hasProperty(String module, String key, String value) { - values.put(module + "." + key, value); - return this; - } - - @Override - public String getProperty(String key) { - return (String) values.get(key); - } - - @Override - public String getProperty(String module, String key) { - return (String) values.get(module + "." + key); - } - - @Override - public boolean getBooleanProperty(String module, String key, boolean defaultValue) { - Boolean value = (Boolean) values.get(module + "." + key); - if (value == null) { - return defaultValue; - } else { - return value; - } - } -} diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedEarliestDateResolver.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedEarliestDateResolver.java deleted file mode 100644 index a585e1427e..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedEarliestDateResolver.java +++ /dev/null @@ -1,29 +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.xoai.tests.helpers.stubs; - -import java.sql.SQLException; -import java.util.Date; - -import org.dspace.core.Context; -import org.dspace.xoai.exceptions.InvalidMetadataFieldException; -import org.dspace.xoai.services.api.EarliestDateResolver; - -public class StubbedEarliestDateResolver implements EarliestDateResolver { - private Date date = new Date(); - - public StubbedEarliestDateResolver is(Date date) { - this.date = date; - return this; - } - - @Override - public Date getEarliestDate(Context context) throws InvalidMetadataFieldException, SQLException { - return date; - } -} diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedResourceResolver.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedResourceResolver.java deleted file mode 100644 index 464f8fc487..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedResourceResolver.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.xoai.tests.helpers.stubs; - -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.Map; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerConfigurationException; -import javax.xml.transform.TransformerFactory; - -import com.lyncode.xoai.dataprovider.services.api.ResourceResolver; - -public class StubbedResourceResolver implements ResourceResolver { - private static TransformerFactory factory = TransformerFactory.newInstance(); - - private Map inputStreamMap = new HashMap(); - private Map transformerMap = new HashMap(); - - @Override - public InputStream getResource(String path) throws IOException { - return inputStreamMap.get(path); - } - - @Override - public Transformer getTransformer(String path) throws IOException, TransformerConfigurationException { - return transformerMap.get(path); - } - - public StubbedResourceResolver hasIdentityTransformerFor(String path) { - try { - transformerMap.put(path, factory.newTransformer()); - } catch (TransformerConfigurationException e) { - throw new RuntimeException(e); - } - return this; - } -} diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedSetRepository.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedSetRepository.java deleted file mode 100644 index 8432633e7b..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedSetRepository.java +++ /dev/null @@ -1,74 +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.xoai.tests.helpers.stubs; - -import static java.lang.Math.min; -import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic; - -import java.util.ArrayList; -import java.util.List; - -import com.lyncode.xoai.dataprovider.core.ListSetsResult; -import com.lyncode.xoai.dataprovider.core.Set; -import com.lyncode.xoai.dataprovider.services.api.SetRepository; - -public class StubbedSetRepository implements SetRepository { - private List sets = new ArrayList(); - private boolean supports = false; - - @Override - public boolean supportSets() { - return supports; - } - - @Override - public ListSetsResult retrieveSets(int offset, int length) { - if (offset > sets.size()) { - return new ListSetsResult(false, new ArrayList(), sets.size()); - } - return new ListSetsResult(offset + length < sets.size(), - sets.subList(offset, min(offset + length, sets.size())), sets.size()); - } - - @Override - public boolean exists(String setSpec) { - for (Set set : sets) { - if (set.getSetSpec().equals(setSpec)) { - return true; - } - } - - return false; - } - - public StubbedSetRepository doesSupportSets() { - this.supports = true; - return this; - } - - public StubbedSetRepository doesNotSupportSets() { - this.supports = false; - return this; - } - - public StubbedSetRepository withSet(String name, String spec) { - this.sets.add(new Set(spec, name)); - return this; - } - - public StubbedSetRepository withRandomlyGeneratedSets(int number) { - for (int i = 0; i < number; i++) { - this.sets.add(new Set(randomAlphabetic(10), randomAlphabetic(10))); - } - return this; - } - - public void clear() { - this.sets.clear(); - } -} diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedXOAIManagerResolver.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedXOAIManagerResolver.java deleted file mode 100644 index f0d3275f74..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedXOAIManagerResolver.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.xoai.tests.helpers.stubs; - -import com.lyncode.xoai.dataprovider.core.XOAIManager; -import com.lyncode.xoai.dataprovider.exceptions.ConfigurationException; -import com.lyncode.xoai.dataprovider.services.api.ResourceResolver; -import com.lyncode.xoai.dataprovider.xml.xoaiconfig.Configuration; -import org.dspace.xoai.services.api.config.XOAIManagerResolver; -import org.dspace.xoai.services.api.config.XOAIManagerResolverException; -import org.dspace.xoai.services.api.xoai.DSpaceFilterResolver; -import org.springframework.beans.factory.annotation.Autowired; - -public class StubbedXOAIManagerResolver implements XOAIManagerResolver { - @Autowired - ResourceResolver resourceResolver; - @Autowired - DSpaceFilterResolver filterResolver; - - private Configuration builder = new Configuration(); - - public Configuration configuration() { - return builder; - } - - @Override - public XOAIManager getManager() throws XOAIManagerResolverException { - try { - return new XOAIManager(filterResolver, resourceResolver, builder); - } catch (ConfigurationException e) { - throw new XOAIManagerResolverException(e); - } - } -} diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/AbstractDSpaceTest.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/AbstractDSpaceTest.java deleted file mode 100644 index d9824d8c53..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/AbstractDSpaceTest.java +++ /dev/null @@ -1,186 +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.xoai.tests.integration.xoai; - -import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; - -import java.util.Date; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.xml.xpath.XPathExpressionException; - -import com.lyncode.builder.MapBuilder; -import com.lyncode.xoai.dataprovider.services.api.ResourceResolver; -import com.lyncode.xoai.dataprovider.services.impl.BaseDateProvider; -import com.lyncode.xoai.dataprovider.xml.xoaiconfig.Configuration; -import com.lyncode.xoai.dataprovider.xml.xoaiconfig.FormatConfiguration; -import org.apache.solr.client.solrj.SolrClient; -import org.dspace.xoai.controller.DSpaceOAIDataProvider; -import org.dspace.xoai.services.api.EarliestDateResolver; -import org.dspace.xoai.services.api.FieldResolver; -import org.dspace.xoai.services.api.config.ConfigurationService; -import org.dspace.xoai.services.api.config.XOAIManagerResolver; -import org.dspace.xoai.tests.DSpaceTestConfiguration; -import org.dspace.xoai.tests.helpers.stubs.ItemRepositoryBuilder; -import org.dspace.xoai.tests.helpers.stubs.StubbedConfigurationService; -import org.dspace.xoai.tests.helpers.stubs.StubbedEarliestDateResolver; -import org.dspace.xoai.tests.helpers.stubs.StubbedFieldResolver; -import org.dspace.xoai.tests.helpers.stubs.StubbedResourceResolver; -import org.dspace.xoai.tests.helpers.stubs.StubbedSetRepository; -import org.dspace.xoai.tests.helpers.stubs.StubbedXOAIManagerResolver; -import org.junit.After; -import org.junit.Before; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.ResultMatcher; -import org.springframework.test.web.servlet.result.MockMvcResultMatchers; -import org.springframework.test.web.servlet.result.XpathResultMatchers; -import org.springframework.web.context.WebApplicationContext; - - -@RunWith(SpringJUnit4ClassRunner.class) -@WebAppConfiguration -@ContextConfiguration(classes = {DSpaceTestConfiguration.class, DSpaceOAIDataProvider.class}) -public abstract class AbstractDSpaceTest { - private static final BaseDateProvider baseDateProvider = new BaseDateProvider(); - @Autowired - WebApplicationContext wac; - private MockMvc mockMvc; - - private StubbedXOAIManagerResolver xoaiManagerResolver; - private StubbedConfigurationService configurationService; - private StubbedFieldResolver databaseService; - private StubbedEarliestDateResolver earliestDateResolver; - private StubbedSetRepository setRepository; - private StubbedResourceResolver resourceResolver; - private SolrClient solrServer; - - @Before - public void setup() { - xoaiManagerResolver = (StubbedXOAIManagerResolver) this.wac.getBean(XOAIManagerResolver.class); - configurationService = (StubbedConfigurationService) this.wac.getBean(ConfigurationService.class); - databaseService = (StubbedFieldResolver) this.wac.getBean(FieldResolver.class); - earliestDateResolver = (StubbedEarliestDateResolver) this.wac.getBean(EarliestDateResolver.class); - setRepository = this.wac.getBean(StubbedSetRepository.class); - setRepository.clear(); -// solrServer = this.wac.getBean(SolrServer.class); - resourceResolver = (StubbedResourceResolver) this.wac.getBean(ResourceResolver.class); - xoaiManagerResolver.configuration(); - } - - @After - public void teardown() { - // Nullify all resources so that JUnit will clean them up - xoaiManagerResolver = null; - configurationService = null; - databaseService = null; - earliestDateResolver = null; - setRepository = null; - resourceResolver = null; - mockMvc = null; - solrServer = null; - } - - protected MockMvc againstTheDataProvider() { - if (this.mockMvc == null) { - this.mockMvc = webAppContextSetup(this.wac).build(); - } - return this.mockMvc; - } - - protected Configuration theConfiguration() { - return xoaiManagerResolver.configuration(); - } - - protected StubbedConfigurationService theDSpaceConfiguration() { - return configurationService; - } - - protected StubbedFieldResolver theDatabase() { - return databaseService; - } - - protected StubbedEarliestDateResolver theEarlistEarliestDate() { - return earliestDateResolver; - } - - protected XpathResultMatchers oaiXPath(String xpath) throws XPathExpressionException { - return MockMvcResultMatchers.xpath(this.replaceXpath(xpath), new MapBuilder() - .withPair("o", "http://www.openarchives.org/OAI/2.0/") - .build()); - } - - private String replaceXpath(String xpath) { - int offset = 0; - String newXpath = ""; - Pattern pattern = Pattern.compile("/[^/]+"); - Matcher matcher = pattern.matcher(xpath); - while (matcher.find()) { - if (matcher.start() > offset) { - newXpath += xpath.substring(offset, matcher.start()); - } - if (!matcher.group().contains(":") && !matcher.group().startsWith("/@")) { - newXpath += "/o:" + matcher.group().substring(1); - } else { - newXpath += matcher.group(); - } - offset = matcher.end() + 1; - } - - return newXpath; - } - - protected String representationOfDate(Date date) { - return baseDateProvider.format(date); - } - - protected StubbedSetRepository theSetRepository() { - return setRepository; - } - - protected com.lyncode.xoai.dataprovider.xml.xoaiconfig.ContextConfiguration aContext(String baseUrl) { - return new com.lyncode.xoai.dataprovider.xml.xoaiconfig.ContextConfiguration(baseUrl); - } - - protected XpathResultMatchers responseDate() throws XPathExpressionException { - return oaiXPath("/OAI-PMH/responseDate"); - } - - protected ResultMatcher verb(org.hamcrest.Matcher matcher) throws XPathExpressionException { - return oaiXPath("/OAI-PMH/request/@verb").string(matcher); - } - - protected XpathResultMatchers resumptionToken() throws XPathExpressionException { - return oaiXPath("//resumptionToken"); - } - - protected StubbedResourceResolver theResourseResolver() { - return resourceResolver; - } - - protected FormatConfiguration aFormat(String id) { - return new FormatConfiguration(id); - } - - protected ItemRepositoryBuilder.DSpaceItemBuilder anItem() { - return new ItemRepositoryBuilder.DSpaceItemBuilder(); - } - - private ItemRepositoryBuilder itemRepositoryBuilder; - - public ItemRepositoryBuilder theItemRepository() { - if (itemRepositoryBuilder == null) { - itemRepositoryBuilder = new ItemRepositoryBuilder(solrServer); - } - return itemRepositoryBuilder; - } -} \ No newline at end of file diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/IdentifyTest.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/IdentifyTest.java deleted file mode 100644 index 173a118f73..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/IdentifyTest.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.xoai.tests.integration.xoai; - -import static org.dspace.xoai.tests.helpers.SyntacticSugar.and; -import static org.dspace.xoai.tests.helpers.SyntacticSugar.given; -import static org.hamcrest.core.Is.is; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import java.util.Date; - -import org.junit.Ignore; -import org.junit.Test; - -@Ignore -public class IdentifyTest extends AbstractDSpaceTest { - - public static final Date EARLIEST_DATE = new Date(); - - @Test - public void requestForIdentifyWithoutRequiredConfigurationAdminEmailSetShouldFail() throws Exception { - given(theDSpaceConfiguration() - .withoutProperty("mail.admin")); - and(given(theConfiguration().withContextConfigurations(aContext("request")))); - - againstTheDataProvider().perform(get("/request?verb=Identify")) - .andExpect(status().isInternalServerError()); - } - - @Test - public void requestForIdentifyShouldReturnTheConfiguredValues() throws Exception { - given(theDSpaceConfiguration() - .hasProperty("dspace.name", "Test") - .hasProperty("mail.admin", "test@test.com")); - - and(given(theEarlistEarliestDate().is(EARLIEST_DATE))); - and(given(theConfiguration().withContextConfigurations(aContext("request")))); - - againstTheDataProvider().perform(get("/request?verb=Identify")) - .andExpect(status().isOk()) - .andExpect(oaiXPath("//repositoryName").string("Test")) - .andExpect(oaiXPath("//adminEmail").string("test@test.com")) - .andExpect( - oaiXPath("//earliestDatestamp").string(is(representationOfDate(EARLIEST_DATE)))); - } -} diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/ListSetsTest.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/ListSetsTest.java deleted file mode 100644 index d0bcd75c03..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/ListSetsTest.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.xoai.tests.integration.xoai; - -import static org.dspace.xoai.tests.helpers.SyntacticSugar.and; -import static org.dspace.xoai.tests.helpers.SyntacticSugar.given; -import static org.hamcrest.core.Is.is; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import org.junit.Ignore; -import org.junit.Test; - -@Ignore -public class ListSetsTest extends AbstractDSpaceTest { - @Test - public void listSetsWithLessSetsThenMaxSetsPerPage() throws Exception { - given(theConfiguration() - .withMaxListSetsSize(100) - .withContextConfigurations(aContext("request"))); - and(given(theSetRepository() - .doesSupportSets() - .withSet("name", "spec"))); - - againstTheDataProvider().perform(get("/request?verb=ListSets")) - .andExpect(status().isOk()) - .andDo(print()) - .andExpect(responseDate().exists()) - .andExpect(verb(is("ListSets"))) - .andExpect(oaiXPath("//set").nodeCount(1)) - .andExpect(oaiXPath("//set/setSpec").string("spec")) - .andExpect(oaiXPath("//set/setName").string("name")) - .andExpect(resumptionToken().doesNotExist()); - } - - @Test - public void listSetsWithMoreSetsThenMaxSetsPerPage() throws Exception { - given(theConfiguration() - .withMaxListSetsSize(10) - .withContextConfigurations(aContext("request"))); - - and(given(theSetRepository() - .doesSupportSets() - .withRandomlyGeneratedSets(20))); - - againstTheDataProvider().perform(get("/request?verb=ListSets")) - .andExpect(status().isOk()) - .andExpect(responseDate().exists()) - .andExpect(verb(is("ListSets"))) - .andExpect(oaiXPath("//set").nodeCount(10)) - .andExpect(resumptionToken().string("////10")) - .andExpect(oaiXPath("//resumptionToken/@completeListSize").number(Double.valueOf(20))); - - and(againstTheDataProvider().perform(get("/request?verb=ListSets&resumptionToken=////10")) - .andExpect(status().isOk()) - .andExpect(responseDate().exists()) - .andExpect(verb(is("ListSets"))) - .andExpect(oaiXPath("//set").nodeCount(10)) - .andExpect(resumptionToken().string(""))); - } -} diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/OAIContextTest.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/OAIContextTest.java deleted file mode 100644 index c96445ac0d..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/OAIContextTest.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.xoai.tests.integration.xoai; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import org.junit.Ignore; -import org.junit.Test; - -@Ignore -public class OAIContextTest extends AbstractDSpaceTest { - public static final String ROOT_URL = "/"; - - @Test - public void requestToRootShouldGiveListOfContextsWithBadRequestError() throws Exception { - againstTheDataProvider().perform(get(ROOT_URL)) - .andDo(print()) - .andExpect(status().isBadRequest()) - .andExpect(model().attributeExists("contexts")); - } - - @Test - public void requestForUnknownContextShouldGiveListOfContextsWithBadRequestError() throws Exception { - againstTheDataProvider().perform(get("/unexistentContext")) - .andDo(print()) - .andExpect(status().isBadRequest()) - .andExpect(model().attributeExists("contexts")); - } -} From ea4e05d8b8ca73735d934fca6c2f6ca704d17e15 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 5 Mar 2019 21:44:07 +0000 Subject: [PATCH 27/52] Embed RDF into Boot webapp. Add basic ITs to prove it works. --- dspace-rdf/pom.xml | 15 +- .../app/configuration/RDFWebConfig.java | 56 +++++++ dspace-rdf/src/main/webapp/WEB-INF/web.xml | 71 --------- dspace-spring-rest/pom.xml | 4 + .../test/java/org/dspace/app/rdf/RdfIT.java | 146 ++++++++++++++++++ dspace/config/modules/rdf.cfg | 25 ++- dspace/modules/rdf/pom.xml | 112 -------------- dspace/modules/rdf/src/main/webapp/.gitignore | 0 dspace/pom.xml | 1 - pom.xml | 1 - 10 files changed, 238 insertions(+), 193 deletions(-) create mode 100644 dspace-rdf/src/main/java/org/dspace/app/configuration/RDFWebConfig.java delete mode 100644 dspace-rdf/src/main/webapp/WEB-INF/web.xml create mode 100644 dspace-spring-rest/src/test/java/org/dspace/app/rdf/RdfIT.java delete mode 100644 dspace/modules/rdf/pom.xml delete mode 100644 dspace/modules/rdf/src/main/webapp/.gitignore diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 9c6c154ddc..080045de03 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -3,9 +3,9 @@ 4.0.0 org.dspace dspace-rdf - war + jar DSpace RDF - Parent project for the RDF API and Webapp + DSpace RDF (Linked Data) Extension org.dspace @@ -58,8 +58,15 @@ dspace-services - + + org.springframework.boot + spring-boot-starter-web + ${spring-boot.version} + + + + javax.servlet diff --git a/dspace-rdf/src/main/java/org/dspace/app/configuration/RDFWebConfig.java b/dspace-rdf/src/main/java/org/dspace/app/configuration/RDFWebConfig.java new file mode 100644 index 0000000000..15b991af0a --- /dev/null +++ b/dspace-rdf/src/main/java/org/dspace/app/configuration/RDFWebConfig.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.configuration; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * RDF webapp configuration. Replaces the old web.xml + *

+ * This @Configuration class is automatically discovered by Spring via a @ComponentScan. + *

+ * All RDF web configurations (beans) can be enabled or disabled by setting "rdf.enabled" + * to true or false, respectively (in your DSpace configuration). Default is "false". + *

+ * All @Value annotated configurations below can also be overridden in your DSpace configuration. + * + * @author Tim Donohue + */ +@Configuration +public class RDFWebConfig { + // Path where RDF should be deployed (when enabled). Defaults to "rdf" + @Value("${rdf.path:rdf}") + private String rdfPath; + + // Servlet Beans. All of the below bean definitions map servlets to respond to specific URL patterns + // These are the combined equivalent of and in web.xml + // All beans are only loaded when rdf.enabled = true + + @Bean + @ConditionalOnProperty("rdf.enabled") + public ServletRegistrationBean rdfSerializationBean() { + ServletRegistrationBean bean = new ServletRegistrationBean( new org.dspace.rdf.providing.DataProviderServlet(), + "/" + rdfPath + "/handle/*"); + bean.setLoadOnStartup(1); + return bean; + } + + @Bean + @ConditionalOnProperty("rdf.enabled") + public ServletRegistrationBean rdfLocalURIRedirectionBean() { + ServletRegistrationBean bean = new ServletRegistrationBean( + new org.dspace.rdf.providing.LocalURIRedirectionServlet(), "/" + rdfPath + "/resource/*"); + bean.setLoadOnStartup(1); + return bean; + } +} + diff --git a/dspace-rdf/src/main/webapp/WEB-INF/web.xml b/dspace-rdf/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index c15ec675f3..0000000000 --- a/dspace-rdf/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - - - RDF Data Provider - - - - The location of the DSpace home directory - - dspace.dir - ${dspace.dir} - - - - log4jConfiguration - ${dspace.dir}/config/log4j2.xml - - The location of the Log4J configuration - - - - - dspace.request - org.dspace.utils.servlet.DSpaceWebappServletFilter - - - - dspace.request - /* - - - - org.dspace.app.util.DSpaceContextListener - - - - org.dspace.servicemanager.servlet.DSpaceKernelServletContextListener - - - - rdf-serialization - org.dspace.rdf.providing.DataProviderServlet - - - - local-uri-redirection - org.dspace.rdf.providing.LocalURIRedirectionServlet - - - - - rdf-serialization - /handle/* - - - - local-uri-redirection - /resource/* - - diff --git a/dspace-spring-rest/pom.xml b/dspace-spring-rest/pom.xml index 581c86062e..b1c6b243ab 100644 --- a/dspace-spring-rest/pom.xml +++ b/dspace-spring-rest/pom.xml @@ -264,6 +264,10 @@ org.dspace dspace-oai + + org.dspace + dspace-rdf + org.dspace dspace-sword diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rdf/RdfIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rdf/RdfIT.java new file mode 100644 index 0000000000..9cc731ad9c --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rdf/RdfIT.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.rdf; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.doReturn; + +import java.net.URI; + +import org.dspace.app.rest.builder.CommunityBuilder; +import org.dspace.app.rest.test.AbstractWebClientIntegrationTest; +import org.dspace.content.Community; +import org.dspace.content.service.SiteService; +import org.dspace.rdf.RDFUtil; +import org.dspace.rdf.conversion.RDFConverter; +import org.dspace.rdf.factory.RDFFactoryImpl; +import org.dspace.rdf.storage.RDFStorage; +import org.dspace.rdf.storage.RDFStorageImpl; +import org.dspace.services.ConfigurationService; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Spy; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.TestPropertySource; + +/** + * Integration test to verify that the /rdf endpoint is responding as a valid RDF endpoint. + * This tests that our dspace-rdf module is running at this endpoint. + *

+ * This is a AbstractWebClientIntegrationTest because testing dspace-rdf requires + * running a web server (as dspace-rdf makes use of Servlets, not Controllers). + *

+ * NOTE: At this time, these ITs do NOT run against a real RDF triplestore. Instead, + * individual tests are expected to mock triplestore responses via a spy-able RDFStorage. + * + * @author Tim Donohue + */ +// Ensure the RDF endpoint IS ENABLED before any tests run. +// This annotation overrides default DSpace config settings loaded into Spring Context +@TestPropertySource(properties = {"rdf.enabled = true"}) +public class RdfIT extends AbstractWebClientIntegrationTest { + + @Autowired + private ConfigurationService configurationService; + + @Autowired + private SiteService siteService; + + @Autowired + private RDFConverter rdfConverter; + + @Autowired + private RDFFactoryImpl rdfFactory; + + // Create a new spy-able instance of RDFStorage. We will use this instance in all below tests (see @Before) + // so that we can fake a triplestore backend. No triplestore is used in these tests. + @Spy + RDFStorage rdfStorage = new RDFStorageImpl(); + + // All RDF paths that we test against + private final String SERIALIZE_PATH = "/rdf/handle"; + private final String REDIRECTION_PATH = "/rdf/resource"; + + @Before + public void onlyRunIfConfigExists() { + // These integration tests REQUIRE that RDFWebConfig is found/available (as this class deploys RDF) + // If this class is not available, the below "Assume" will cause all tests to be SKIPPED + // NOTE: RDFWebConfig is provided by the 'dspace-rdf' module + try { + Class.forName("org.dspace.app.configuration.RDFWebConfig"); + } catch (ClassNotFoundException ce) { + Assume.assumeNoException(ce); + } + + // Change the running RDFFactory to use our spy-able, default instance of RDFStorage + // Again, this lets us fake a triplestore backend in individual tests below. + rdfFactory.setStorage(rdfStorage); + } + + @Test + public void serializationTest() throws Exception { + //Turn off the authorization system, otherwise we can't make the objects + context.turnOffAuthorisationSystem(); + + // Create a Community + Community community = CommunityBuilder.createCommunity(context) + .withName("Test Community") + .build(); + // Ensure Community is written to test DB immediately + context.commit(); + context.restoreAuthSystemState(); + + // Get the RDF identifiers for this new Community & our Site object + String communityIdentifier = RDFUtil.generateIdentifier(context, community); + String siteIdentifier = RDFUtil.generateIdentifier(context, siteService.findSite(context)); + + // Mock an RDF triplestore's response by returning the RDF conversion of our Community + // when rdfStorage.load() is called with the RDF identifier for this Community + doReturn(rdfConverter.convert(context, community)).when(rdfStorage).load(communityIdentifier); + + // Perform a GET request on the RDF /handle path, using our new Community's Handle + ResponseEntity response = getResponseAsString(SERIALIZE_PATH + "/" + community.getHandle()); + // Expect a 200 response code, and text/turtle (RDF Turtle syntax) response + assertThat(response.getStatusCode(), equalTo(HttpStatus.OK)); + assertThat(response.getHeaders().getContentType().toString(), equalTo("text/turtle;charset=UTF-8")); + + // Turtle response should include the RDF identifier of Community + assertThat(response.getBody(), containsString(communityIdentifier)); + // Turtle response should also note that this Community is part of our Site object + assertThat(response.getBody(), containsString("dspace:isPartOfRepository <" + siteIdentifier + "> ;")); + } + + @Test + public void redirectionTest() throws Exception { + //Turn off the authorization system, otherwise we can't make the objects + context.turnOffAuthorisationSystem(); + + // Create a Community + Community community = CommunityBuilder.createCommunity(context) + .withName("Test Community") + .build(); + // Ensure Community is written to test DB immediately (so that a lookup via handle will succeed below) + context.commit(); + context.restoreAuthSystemState(); + + String communityHandle = community.getHandle(); + + // Perform a GET request on the RDF /resource path, using this Community's Handle + ResponseEntity response = getResponseAsString(REDIRECTION_PATH + "/" + communityHandle); + // Expect a 303 (See Other) response code, redirecting us to the HTTP URI of the Community + assertThat(response.getStatusCode(), equalTo(HttpStatus.SEE_OTHER)); + // Expect location of redirection to be [dspace.url]/handle/[community_handle] + assertThat(response.getHeaders().getLocation(), equalTo( + URI.create(configurationService.getProperty("dspace.url") + "/handle/" + communityHandle + "/"))); + } +} diff --git a/dspace/config/modules/rdf.cfg b/dspace/config/modules/rdf.cfg index 355d9f65c0..f33a320360 100644 --- a/dspace/config/modules/rdf.cfg +++ b/dspace/config/modules/rdf.cfg @@ -1,12 +1,29 @@ -# These configs are used by dspace-rdf and the buildin Linked Data export (rdfizer) +#---------------------------------------------------------------# +#----------------RDF (LINKED DATA) CONFIGURATIONS---------------# +#---------------------------------------------------------------# +# These configs are only used by the RDF (Linked Data) # +# interface (provided by dspace-rdf and the built-in Linked # +# Data export (rdfizer)). +#---------------------------------------------------------------# + +# Whether or not to enable the RDF module +# When "true", the RDF module is accessible on ${rdf.path} +# When "false" or commented out, RDF is disabled/inaccessible. +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +#rdf.enabled = true + +# Path where RDF module is available (in the Spring REST webapp) +# Defaults to "rdf", which means the RDF module would be available +# at ${dspace.restURL}/rdf/ +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +rdf.path = rdf # Configure if content negotiation should be enabled rdf.contentNegotiation.enable = false -# Set the url of the dspace-rdf module here. This is necessary to use content +# Set the url of the RDF module here. This is necessary to use content # negotiation -rdf.contextPath = ${dspace.baseUrl}/rdf - +rdf.contextPath = ${dspace.baseUrl}/${rdf.path} # Address of the public SPARQL endpoint diff --git a/dspace/modules/rdf/pom.xml b/dspace/modules/rdf/pom.xml deleted file mode 100644 index 297d165a6d..0000000000 --- a/dspace/modules/rdf/pom.xml +++ /dev/null @@ -1,112 +0,0 @@ - - 4.0.0 - org.dspace.modules - rdf - war - DSpace RDF :: Local Customizations - - Overlay RDF customizations - - - - modules - org.dspace - 7.0-SNAPSHOT - .. - - - - - ${basedir}/../../.. - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - unpack - prepare-package - - unpack-dependencies - - - org.dspace.modules - additions - - ${project.build.directory}/additions - META-INF/** - - - - - - org.apache.maven.plugins - maven-war-plugin - - false - - true - - - - ${project.build.directory}/additions - WEB-INF/classes - - - - - - prepare-package - - - - - - - - - oracle-support - - - db.name - oracle - - - - - com.oracle - ojdbc6 - - - - - - - - org.dspace.modules - additions - - - org.dspace - dspace-rdf - war - - - javax.servlet - javax.servlet-api - provided - - - - diff --git a/dspace/modules/rdf/src/main/webapp/.gitignore b/dspace/modules/rdf/src/main/webapp/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/dspace/pom.xml b/dspace/pom.xml index 0c6ea85f78..bf46f313bf 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -203,7 +203,6 @@ org.dspace dspace-rdf - war compile diff --git a/pom.xml b/pom.xml index 4bf4365de5..2a2905b9f2 100644 --- a/pom.xml +++ b/pom.xml @@ -962,7 +962,6 @@ org.dspace dspace-rdf 7.0-SNAPSHOT - war org.dspace From 4fd0044ff84bddfe8d20c005770bf1d397fc0696 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 21 Mar 2019 18:24:46 +0000 Subject: [PATCH 28/52] Bug Fix. Ensure proper "baseUrl" is reported from each OAI context. Add IT to prove it works --- .../impl/xoai/DSpaceRepositoryConfiguration.java | 16 ++++++++++++++-- .../test/java/org/dspace/app/oai/OAIpmhIT.java | 3 +++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/xoai/DSpaceRepositoryConfiguration.java b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/xoai/DSpaceRepositoryConfiguration.java index 979520c199..fe7fbdbfd0 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/xoai/DSpaceRepositoryConfiguration.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/xoai/DSpaceRepositoryConfiguration.java @@ -27,6 +27,8 @@ import org.dspace.xoai.services.api.EarliestDateResolver; import org.dspace.xoai.services.api.config.ConfigurationService; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import org.springframework.web.util.UriComponentsBuilder; /** * @author Lyncode Development Team (dspace at lyncode dot com) @@ -68,17 +70,27 @@ public class DSpaceRepositoryConfiguration implements RepositoryConfiguration { public String getBaseUrl() { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()) .getRequest(); + + // Parse the current OAI "context" path out of the last HTTP request. + // (e.g. for "http://mydspace.edu/oai/request", the oaiContextPath is "request") + UriComponentsBuilder builder = ServletUriComponentsBuilder.fromRequest(request); + List pathSegments = builder.buildAndExpand().getPathSegments(); + String oaiContextPath = pathSegments.get(pathSegments.size() - 1); + if (baseUrl == null) { baseUrl = configurationService.getProperty("oai.url"); if (baseUrl == null) { log.warn( "{ OAI 2.0 :: DSpace } Not able to retrieve the oai.url property from oai.cfg. Falling back to " + "request address"); + // initialize baseUrl to a fallback "oai.url" which is the current request with OAI context removed. baseUrl = request.getRequestURL().toString() - .replace(request.getPathInfo(), ""); + .replace(oaiContextPath, ""); } } - return baseUrl + request.getPathInfo(); + + // BaseURL is the path of OAI with the current OAI context appended + return baseUrl + "/" + oaiContextPath; } @Override diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/oai/OAIpmhIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/oai/OAIpmhIT.java index 3869baefab..57590f8166 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/oai/OAIpmhIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/oai/OAIpmhIT.java @@ -162,6 +162,9 @@ public class OAIpmhIT extends AbstractControllerIntegrationTest { // Expect adminEmail to be the same as "mail.admin" config .andExpect(xpath("OAI-PMH/Identify/adminEmail") .string(configurationService.getProperty("mail.admin"))) + // Expect baseURL to be the same as our "oai.url" with the DEFAULT_CONTEXT_PATH appended + .andExpect(xpath("OAI-PMH/Identify/baseURL") + .string(configurationService.getProperty("oai.url") + "/" + DEFAULT_CONTEXT_PATH)) // Expect earliestDatestamp to be "now", i.e. current date, (as mocked above) .andExpect(xpath("OAI-PMH/Identify/earliestDatestamp") .string(baseDateProvider.format(now))) From 4938eb7f42ac7362657bc9737f0e063d97814b97 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 21 Mar 2019 18:25:37 +0000 Subject: [PATCH 29/52] Configuration fixes. Add missing oai.cfg properties. Default dspace.baseUrl to single webapp path. --- dspace/config/dspace.cfg | 4 ++-- dspace/config/modules/oai.cfg | 14 +++++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index a290d7f36e..6b2581a497 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -26,7 +26,7 @@ dspace.dir = /dspace dspace.hostname = localhost # DSpace base host URL. Include port number etc. -dspace.baseUrl = http://localhost:8080 +dspace.baseUrl = http://localhost:8080/spring-rest # Full link your end users will use to access DSpace. In most cases, this will be the baseurl followed by # the context path to the UI you are using. @@ -37,7 +37,7 @@ dspace.url = ${dspace.baseUrl} # This is the URL that will be used for the REST endpoints to be served on. # This will typically be followed by /api to determine the root endpoints. -dspace.restUrl = ${dspace.baseUrl}/spring-rest +dspace.restUrl = ${dspace.baseUrl} # Optional: DSpace URL for mobile access # This diff --git a/dspace/config/modules/oai.cfg b/dspace/config/modules/oai.cfg index 493eb2510e..0765331c58 100644 --- a/dspace/config/modules/oai.cfg +++ b/dspace/config/modules/oai.cfg @@ -4,12 +4,24 @@ # These configs are used by the OAI-PMH interface # #---------------------------------------------------------------# +# Whether or not to enable the OAI module +# When "true", the OAI module is accessible on ${oai.path} +# When "false" or commented out, OAI is disabled/inaccessible. +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +#oai.enabled = true + +# Path where OAI module is available +# Defaults to "oai", which means the OAI module would be available +# at ${dspace.restURL}/oai/ +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +oai.path = oai + # Storage: solr | database (solr is recommended) oai.storage=solr # The base URL of the OAI webapp (do not include the context e.g. /request, /openaire, etc). # Note: Comment out if you want to fallback to the request's URL. -oai.url = ${dspace.baseUrl}/oai +oai.url = ${dspace.baseUrl}/${oai.path} # Base solr index oai.solr.url=${solr.server}/oai From f89eebc71425f50c98c4bbe0f43a6f182b172107 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 21 Mar 2019 20:15:30 +0000 Subject: [PATCH 30/52] Dependency bug fix. Avoid pulling in Spring Boot logging & other unnecessary dependencies --- dspace-oai/pom.xml | 10 ++++++++-- dspace-rdf/pom.xml | 24 +++++++----------------- dspace-sword/pom.xml | 8 +++++++- dspace-swordv2/pom.xml | 8 +++++++- 4 files changed, 29 insertions(+), 21 deletions(-) diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index fd813b2e9c..8bbc89e0b8 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -111,8 +111,14 @@ org.springframework.boot - spring-boot-starter-web + spring-boot-starter ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-logging + + @@ -121,7 +127,7 @@ jtwig-spring-boot-starter ${jtwig.version} - + org.springframework.boot spring-boot-starter-web diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 080045de03..bcfd0ecb2f 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -61,26 +61,16 @@ org.springframework.boot - spring-boot-starter-web + spring-boot-starter ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-logging + + - - - javax.servlet javax.servlet-api diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 938e9dbb04..f9607c4a7f 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -77,8 +77,14 @@ org.springframework.boot - spring-boot-starter-web + spring-boot-starter ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-logging + + diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 998ac3436b..4d457f2904 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -98,8 +98,14 @@ org.springframework.boot - spring-boot-starter-web + spring-boot-starter ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-logging + + From efea77fb7d780480f3f9dc73b72079a432c45a97 Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Wed, 27 Mar 2019 12:44:09 -0700 Subject: [PATCH 31/52] simplify dockerfile for onewebapp --- Dockerfile.jdk8 | 9 +- Dockerfile.jdk8-test | 13 +- Dockerfile.onewebapp.jdk8 | 57 ++++++ dspace/src/main/docker/test/solr_web.xml | 219 ----------------------- 4 files changed, 62 insertions(+), 236 deletions(-) create mode 100644 Dockerfile.onewebapp.jdk8 delete mode 100644 dspace/src/main/docker/test/solr_web.xml diff --git a/Dockerfile.jdk8 b/Dockerfile.jdk8 index afe2285f49..da07864c72 100644 --- a/Dockerfile.jdk8 +++ b/Dockerfile.jdk8 @@ -54,10 +54,5 @@ EXPOSE 8080 8009 ENV JAVA_OPTS=-Xmx2000m -RUN ln -s $DSPACE_INSTALL/webapps/solr /usr/local/tomcat/webapps/solr && \ - ln -s $DSPACE_INSTALL/webapps/spring-rest /usr/local/tomcat/webapps/spring-rest && \ - ln -s $DSPACE_INSTALL/webapps/rest /usr/local/tomcat/webapps/rest && \ - ln -s $DSPACE_INSTALL/webapps/oai /usr/local/tomcat/webapps/oai && \ - ln -s $DSPACE_INSTALL/webapps/rdf /usr/local/tomcat/webapps/rdf && \ - ln -s $DSPACE_INSTALL/webapps/sword /usr/local/tomcat/webapps/sword && \ - ln -s $DSPACE_INSTALL/webapps/swordv2 /usr/local/tomcat/webapps/swordv2 +RUN ln -s $DSPACE_INSTALL/webapps/spring-rest /usr/local/tomcat/webapps/spring-rest && \ + ln -s $DSPACE_INSTALL/webapps/rest /usr/local/tomcat/webapps/rest diff --git a/Dockerfile.jdk8-test b/Dockerfile.jdk8-test index 01697a045c..0e3d0b7c8c 100644 --- a/Dockerfile.jdk8-test +++ b/Dockerfile.jdk8-test @@ -54,16 +54,9 @@ EXPOSE 8080 8009 ENV JAVA_OPTS=-Xmx2000m -RUN ln -s $DSPACE_INSTALL/webapps/solr /usr/local/tomcat/webapps/solr && \ - ln -s $DSPACE_INSTALL/webapps/spring-rest /usr/local/tomcat/webapps/spring-rest && \ - ln -s $DSPACE_INSTALL/webapps/rest /usr/local/tomcat/webapps/rest && \ - ln -s $DSPACE_INSTALL/webapps/oai /usr/local/tomcat/webapps/oai && \ - ln -s $DSPACE_INSTALL/webapps/rdf /usr/local/tomcat/webapps/rdf && \ - ln -s $DSPACE_INSTALL/webapps/sword /usr/local/tomcat/webapps/sword && \ - ln -s $DSPACE_INSTALL/webapps/swordv2 /usr/local/tomcat/webapps/swordv2 +RUN ln -s $DSPACE_INSTALL/webapps/spring-rest /usr/local/tomcat/webapps/spring-rest && \ + ln -s $DSPACE_INSTALL/webapps/rest /usr/local/tomcat/webapps/rest -COPY dspace/src/main/docker/test/solr_web.xml $DSPACE_INSTALL/webapps/solr/WEB-INF/web.xml COPY dspace/src/main/docker/test/rest_web.xml $DSPACE_INSTALL/webapps/rest/WEB-INF/web.xml -RUN sed -i -e "s|\${dspace.dir}|$DSPACE_INSTALL|" $DSPACE_INSTALL/webapps/solr/WEB-INF/web.xml && \ - sed -i -e "s|\${dspace.dir}|$DSPACE_INSTALL|" $DSPACE_INSTALL/webapps/rest/WEB-INF/web.xml +RUN sed -i -e "s|\${dspace.dir}|$DSPACE_INSTALL|" $DSPACE_INSTALL/webapps/rest/WEB-INF/web.xml diff --git a/Dockerfile.onewebapp.jdk8 b/Dockerfile.onewebapp.jdk8 new file mode 100644 index 0000000000..6cf44fafb2 --- /dev/null +++ b/Dockerfile.onewebapp.jdk8 @@ -0,0 +1,57 @@ +# This image will be published as dspace/dspace +# See https://dspace-labs.github.io/DSpace-Docker-Images/ for usage details +# +# This version is JDK8 compatible +# - tomcat:8-jre8 +# - ANT 1.10.5 +# - maven:3-jdk-8 +# - note: +# - default tag for branch: dspace/dspace: dspace/dspace:dspace-7_x-jdk8 + +# Step 1 - Run Maven Build +FROM dspace/dspace-dependencies:dspace-7_x as build +ARG TARGET_DIR=dspace-installer +WORKDIR /app + +# The dspace-install directory will be written to /install +RUN mkdir /install \ + && chown -Rv dspace: /install \ + && chown -Rv dspace: /app + +USER dspace + +# Copy the DSpace source code into the workdir (excluding .dockerignore contents) +ADD --chown=dspace . /app/ +COPY dspace/src/main/docker/local.cfg /app/local.cfg + +# Build DSpace. Copy the dspace-install directory to /install. Clean up the build to keep the docker image small +RUN mvn package && \ + mv /app/dspace/target/${TARGET_DIR}/* /install && \ + mvn clean + +# Step 2 - Run Ant Deploy +FROM tomcat:8-jre8 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.5 +ENV ANT_HOME /tmp/ant-$ANT_VERSION +ENV PATH $ANT_HOME/bin:$PATH + +RUN mkdir $ANT_HOME && \ + wget -qO- "https://www.apache.org/dist/ant/binaries/apache-ant-$ANT_VERSION-bin.tar.gz" | tar -zx --strip-components=1 -C $ANT_HOME + +RUN ant init_installation update_configs update_code update_webapps update_solr_indexes + +# Step 3 - Run tomcat +# Create a new tomcat image that does not retain the the build directory contents +FROM tomcat:8-jre8 +ENV DSPACE_INSTALL=/dspace +COPY --from=ant_build /dspace $DSPACE_INSTALL +EXPOSE 8080 8009 + +ENV JAVA_OPTS=-Xmx2000m + +RUN ln -s $DSPACE_INSTALL/webapps/ROOT /usr/local/tomcat/webapps/spring-rest \ No newline at end of file diff --git a/dspace/src/main/docker/test/solr_web.xml b/dspace/src/main/docker/test/solr_web.xml deleted file mode 100644 index 50a8bd5b9a..0000000000 --- a/dspace/src/main/docker/test/solr_web.xml +++ /dev/null @@ -1,219 +0,0 @@ - - - - - - - - - - Solr home: configuration, cores etc. - solr/home - ${dspace.dir}/solr - java.lang.String - - - - - - - URL locating a Log4J configuration file (properties or XML). - - log4jConfiguration - ${dspace.dir}/config/log4j-solr.xml - - - - org.apache.logging.log4j.web.Log4jServletContextListener - - - - Activate logging - log4jServletFilter - org.apache.logging.log4j.web.Log4jServletFilter - - - - - LocalHostRestrictionFilter - org.dspace.solr.filters.LocalHostRestrictionFilter - - - - - SolrRequestFilter - org.apache.solr.servlet.SolrDispatchFilter - - - - - - log4jServletFilter - /* - REQUEST - FORWARD - INCLUDE - ERROR - - - - - - - SolrRequestFilter - /* - - - - - - Zookeeper - org.apache.solr.servlet.ZookeeperInfoServlet - - - - LoadAdminUI - org.apache.solr.servlet.LoadAdminUiServlet - - - - - - RedirectOldAdminUI - org.apache.solr.servlet.RedirectServlet - - destination - ${context}/#/ - - - - - RedirectOldZookeeper - org.apache.solr.servlet.RedirectServlet - - destination - ${context}/zookeeper - - - - - RedirectLogging - org.apache.solr.servlet.RedirectServlet - - destination - ${context}/#/~logging - - - - - SolrRestApi - org.restlet.ext.servlet.ServerServlet - - org.restlet.application - org.apache.solr.rest.SolrRestApi - - - - - RedirectOldAdminUI - /admin/ - - - RedirectOldAdminUI - /admin - - - RedirectOldZookeeper - /zookeeper.jsp - - - RedirectLogging - /logging - - - - - Zookeeper - /zookeeper - - - - LoadAdminUI - /admin.html - - - - SolrRestApi - /schema/* - - - - .xsl - - application/xslt+xml - - - - admin.html - - - From 07029b9cafe53b5de8dab88e2e09a83a65417868 Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Wed, 27 Mar 2019 14:19:16 -0700 Subject: [PATCH 32/52] fix ROOT ref --- Dockerfile.onewebapp.jdk8 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.onewebapp.jdk8 b/Dockerfile.onewebapp.jdk8 index 6cf44fafb2..21ec706b50 100644 --- a/Dockerfile.onewebapp.jdk8 +++ b/Dockerfile.onewebapp.jdk8 @@ -54,4 +54,4 @@ EXPOSE 8080 8009 ENV JAVA_OPTS=-Xmx2000m -RUN ln -s $DSPACE_INSTALL/webapps/ROOT /usr/local/tomcat/webapps/spring-rest \ No newline at end of file +RUN ln -s $DSPACE_INSTALL/webapps/spring-rest /usr/local/tomcat/webapps/ROOT \ No newline at end of file From 10b25af15830dce04ed8d4b026ce12908592fffb Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Wed, 27 Mar 2019 16:09:09 -0700 Subject: [PATCH 33/52] handle root --- Dockerfile.onewebapp.jdk8 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile.onewebapp.jdk8 b/Dockerfile.onewebapp.jdk8 index 21ec706b50..096094fdc0 100644 --- a/Dockerfile.onewebapp.jdk8 +++ b/Dockerfile.onewebapp.jdk8 @@ -54,4 +54,5 @@ EXPOSE 8080 8009 ENV JAVA_OPTS=-Xmx2000m -RUN ln -s $DSPACE_INSTALL/webapps/spring-rest /usr/local/tomcat/webapps/ROOT \ No newline at end of file +RUN mv /usr/local/tomcat/ROOT /usr/local/tomcat/ROOT.bk && \ + ln -s $DSPACE_INSTALL/webapps/spring-rest /usr/local/tomcat/webapps/ROOT \ No newline at end of file From 4b942086765cc1284b40fa5ee7b896677e0ce1c2 Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Wed, 27 Mar 2019 16:39:46 -0700 Subject: [PATCH 34/52] fix ROOT --- Dockerfile.onewebapp.jdk8 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.onewebapp.jdk8 b/Dockerfile.onewebapp.jdk8 index 096094fdc0..5098525c61 100644 --- a/Dockerfile.onewebapp.jdk8 +++ b/Dockerfile.onewebapp.jdk8 @@ -54,5 +54,5 @@ EXPOSE 8080 8009 ENV JAVA_OPTS=-Xmx2000m -RUN mv /usr/local/tomcat/ROOT /usr/local/tomcat/ROOT.bk && \ +RUN mv /usr/local/tomcat/webapps/ROOT /usr/local/tomcat/webapps/ROOT.bk && \ ln -s $DSPACE_INSTALL/webapps/spring-rest /usr/local/tomcat/webapps/ROOT \ No newline at end of file From 7bf2d45d146d12c3e702d88108a447c0f349174e Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Thu, 28 Mar 2019 11:47:00 -0700 Subject: [PATCH 35/52] simplify dockerfile overrides --- Dockerfile.jdk8 | 4 ++- Dockerfile.jdk8-test | 4 ++- Dockerfile.onewebapp.jdk8 | 58 --------------------------------------- 3 files changed, 6 insertions(+), 60 deletions(-) delete mode 100644 Dockerfile.onewebapp.jdk8 diff --git a/Dockerfile.jdk8 b/Dockerfile.jdk8 index da07864c72..b5c108d129 100644 --- a/Dockerfile.jdk8 +++ b/Dockerfile.jdk8 @@ -54,5 +54,7 @@ EXPOSE 8080 8009 ENV JAVA_OPTS=-Xmx2000m -RUN ln -s $DSPACE_INSTALL/webapps/spring-rest /usr/local/tomcat/webapps/spring-rest && \ +RUN mv /usr/local/tomcat/webapps/ROOT /usr/local/tomcat/webapps/ROOT.bk && \ + ln -s $DSPACE_INSTALL/webapps/spring-rest /usr/local/tomcat/webapps/ROOT && \ + ln -s $DSPACE_INSTALL/webapps/spring-rest /usr/local/tomcat/webapps/spring-rest && \ ln -s $DSPACE_INSTALL/webapps/rest /usr/local/tomcat/webapps/rest diff --git a/Dockerfile.jdk8-test b/Dockerfile.jdk8-test index 0e3d0b7c8c..54ef4d105f 100644 --- a/Dockerfile.jdk8-test +++ b/Dockerfile.jdk8-test @@ -54,7 +54,9 @@ EXPOSE 8080 8009 ENV JAVA_OPTS=-Xmx2000m -RUN ln -s $DSPACE_INSTALL/webapps/spring-rest /usr/local/tomcat/webapps/spring-rest && \ +RUN mv /usr/local/tomcat/webapps/ROOT /usr/local/tomcat/webapps/ROOT.bk && \ + ln -s $DSPACE_INSTALL/webapps/spring-rest /usr/local/tomcat/webapps/ROOT && \ + ln -s $DSPACE_INSTALL/webapps/spring-rest /usr/local/tomcat/webapps/spring-rest && \ ln -s $DSPACE_INSTALL/webapps/rest /usr/local/tomcat/webapps/rest COPY dspace/src/main/docker/test/rest_web.xml $DSPACE_INSTALL/webapps/rest/WEB-INF/web.xml diff --git a/Dockerfile.onewebapp.jdk8 b/Dockerfile.onewebapp.jdk8 deleted file mode 100644 index 5098525c61..0000000000 --- a/Dockerfile.onewebapp.jdk8 +++ /dev/null @@ -1,58 +0,0 @@ -# This image will be published as dspace/dspace -# See https://dspace-labs.github.io/DSpace-Docker-Images/ for usage details -# -# This version is JDK8 compatible -# - tomcat:8-jre8 -# - ANT 1.10.5 -# - maven:3-jdk-8 -# - note: -# - default tag for branch: dspace/dspace: dspace/dspace:dspace-7_x-jdk8 - -# Step 1 - Run Maven Build -FROM dspace/dspace-dependencies:dspace-7_x as build -ARG TARGET_DIR=dspace-installer -WORKDIR /app - -# The dspace-install directory will be written to /install -RUN mkdir /install \ - && chown -Rv dspace: /install \ - && chown -Rv dspace: /app - -USER dspace - -# Copy the DSpace source code into the workdir (excluding .dockerignore contents) -ADD --chown=dspace . /app/ -COPY dspace/src/main/docker/local.cfg /app/local.cfg - -# Build DSpace. Copy the dspace-install directory to /install. Clean up the build to keep the docker image small -RUN mvn package && \ - mv /app/dspace/target/${TARGET_DIR}/* /install && \ - mvn clean - -# Step 2 - Run Ant Deploy -FROM tomcat:8-jre8 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.5 -ENV ANT_HOME /tmp/ant-$ANT_VERSION -ENV PATH $ANT_HOME/bin:$PATH - -RUN mkdir $ANT_HOME && \ - wget -qO- "https://www.apache.org/dist/ant/binaries/apache-ant-$ANT_VERSION-bin.tar.gz" | tar -zx --strip-components=1 -C $ANT_HOME - -RUN ant init_installation update_configs update_code update_webapps update_solr_indexes - -# Step 3 - Run tomcat -# Create a new tomcat image that does not retain the the build directory contents -FROM tomcat:8-jre8 -ENV DSPACE_INSTALL=/dspace -COPY --from=ant_build /dspace $DSPACE_INSTALL -EXPOSE 8080 8009 - -ENV JAVA_OPTS=-Xmx2000m - -RUN mv /usr/local/tomcat/webapps/ROOT /usr/local/tomcat/webapps/ROOT.bk && \ - ln -s $DSPACE_INSTALL/webapps/spring-rest /usr/local/tomcat/webapps/ROOT \ No newline at end of file From 11842b5485d7363e185d02315c4a361b308ce206 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 18 Apr 2019 15:20:01 -0500 Subject: [PATCH 36/52] Restore dependency that was accidentally removed --- dspace-oai/pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 8bbc89e0b8..7771d37411 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -108,6 +108,13 @@ + + + javax.inject + javax.inject + 1 + + org.springframework.boot From 4d35e0fe286ded50621c3954628e3e7dbc84b7c4 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 9 May 2019 17:05:38 -0500 Subject: [PATCH 37/52] Exclude old version of Jena from SWORDv2 to resolve conflicts with RDF. Add note about upgrading Jena for future. --- dspace-swordv2/pom.xml | 5 +++++ pom.xml | 2 ++ 2 files changed, 7 insertions(+) diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 4d457f2904..d2896bed21 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -86,6 +86,11 @@ org.slf4j slf4j-log4j12 + + + com.hp.hpl.jena + jena + diff --git a/pom.xml b/pom.xml index 2a2905b9f2..fe79dd3ccc 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,8 @@ 1.8 3.17 42.2.1 + 2.13.0 2.6.2 1.7.22 From 50db4b8fc7a231625ef53199bb9f81b812c263bf Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Fri, 12 Apr 2019 14:48:25 +0200 Subject: [PATCH 38/52] [DS-4217] applied the name refactor and rewrote BadRequestException to use this class instead --- .../dspace/app/rest/RestResourceController.java | 4 ++-- ...ption.java => DSpaceBadRequestException.java} | 11 ++++------- .../repository/CollectionRestRepository.java | 6 +++--- .../rest/repository/CommunityRestRepository.java | 4 ++-- .../rest/repository/DSpaceRestRepository.java | 8 ++++---- .../app/rest/repository/ItemRestRepository.java | 6 +++--- .../repository/MetadataFieldRestRepository.java | 4 ++-- .../repository/MetadataSchemaRestRepository.java | 4 ++-- .../repository/WorkflowItemRestRepository.java | 6 +++--- .../repository/WorkspaceItemRestRepository.java | 6 +++--- .../repository/patch/AbstractResourcePatch.java | 16 ++++++++-------- .../app/rest/repository/patch/EPersonPatch.java | 4 ++-- .../app/rest/repository/patch/ItemPatch.java | 4 ++-- .../patch/factories/EPersonOperationFactory.java | 6 +++--- .../patch/factories/ItemOperationFactory.java | 6 +++--- .../impl/EPersonCertificateReplaceOperation.java | 4 ++-- .../impl/EPersonLoginReplaceOperation.java | 4 ++-- .../impl/EPersonNetidReplaceOperation.java | 4 ++-- .../impl/ItemDiscoverableReplaceOperation.java | 4 ++-- .../impl/ItemWithdrawReplaceOperation.java | 4 ++-- .../patch/factories/impl/PatchOperation.java | 11 ++++++----- .../factories/impl/ReplacePatchOperation.java | 8 ++++---- .../factories/impl/ResourcePatchOperation.java | 4 ++-- 23 files changed, 68 insertions(+), 70 deletions(-) rename dspace-spring-rest/src/main/java/org/dspace/app/rest/exception/{PatchBadRequestException.java => DSpaceBadRequestException.java} (55%) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java index 0d932690cd..1a359947aa 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java @@ -31,8 +31,8 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.atteo.evo.inflector.English; import org.dspace.app.rest.converter.JsonPatchConverter; +import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.PaginationException; -import org.dspace.app.rest.exception.PatchBadRequestException; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.exception.RepositoryNotFoundException; import org.dspace.app.rest.exception.RepositorySearchMethodNotFoundException; @@ -729,7 +729,7 @@ public class RestResourceController implements InitializingBean { Patch patch = patchConverter.convert(jsonNode); modelObject = repository.patch(request, apiCategory, model, id, patch); } catch (RepositoryMethodNotImplementedException | UnprocessableEntityException | - PatchBadRequestException | ResourceNotFoundException e) { + DSpaceBadRequestException | ResourceNotFoundException e) { log.error(e.getMessage(), e); throw e; } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/exception/PatchBadRequestException.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/exception/DSpaceBadRequestException.java similarity index 55% rename from dspace-spring-rest/src/main/java/org/dspace/app/rest/exception/PatchBadRequestException.java rename to dspace-spring-rest/src/main/java/org/dspace/app/rest/exception/DSpaceBadRequestException.java index 2000684fb2..4bf6fbc61b 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/exception/PatchBadRequestException.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/exception/DSpaceBadRequestException.java @@ -11,21 +11,18 @@ import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; /** - * Malformed patch document (taken from rfc5789#section-2.2) - When the server - * determines that the patch document provided by the client is not properly - * formatted, it SHOULD return a 400 (Bad Request) response. The definition of - * badly formatted depends on the patch document chosen. + * When a request is malformed, we use this exception to indicate this to the client * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Bad Request") -public class PatchBadRequestException extends RuntimeException { +public class DSpaceBadRequestException extends RuntimeException { - public PatchBadRequestException(String message) { + public DSpaceBadRequestException(String message) { this(message, null); } - public PatchBadRequestException(String message, Exception e) { + public DSpaceBadRequestException(String message, Exception e) { super(message, e); } } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java index a7ba1a5552..cceb32b546 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java @@ -14,7 +14,6 @@ import java.util.List; import java.util.UUID; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.BadRequestException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -23,6 +22,7 @@ import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.converter.CollectionConverter; import org.dspace.app.rest.converter.MetadataConverter; +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.CollectionRest; @@ -191,7 +191,7 @@ public class CollectionRestRepository extends DSpaceObjectRestRepository!"); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java index 6f2c4e8179..7c783da007 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java @@ -26,7 +26,7 @@ import org.apache.logging.log4j.Logger; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.converter.WorkspaceItemConverter; -import org.dspace.app.rest.exception.PatchBadRequestException; +import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.model.ErrorRest; import org.dspace.app.rest.model.WorkspaceItemRest; import org.dspace.app.rest.model.hateoas.WorkspaceItemResource; @@ -280,7 +280,7 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository!"); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/AbstractResourcePatch.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/AbstractResourcePatch.java index 547e33842d..0a3b272d68 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/AbstractResourcePatch.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/AbstractResourcePatch.java @@ -9,7 +9,7 @@ package org.dspace.app.rest.repository.patch; import java.util.List; -import org.dspace.app.rest.exception.PatchBadRequestException; +import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.RestModel; import org.dspace.app.rest.model.patch.Operation; @@ -28,7 +28,7 @@ public abstract class AbstractResourcePatch { * @param restModel the rest resource to patch * @param operations list of patch operations * @throws UnprocessableEntityException - * @throws PatchBadRequestException + * @throws DSpaceBadRequestException */ public R patch(R restModel, List operations) { @@ -53,7 +53,7 @@ public abstract class AbstractResourcePatch { continue ops; default: // JsonPatchConverter should have thrown error before this point. - throw new PatchBadRequestException("Missing or illegal patch operation: " + op.getOp()); + throw new DSpaceBadRequestException("Missing or illegal patch operation: " + op.getOp()); } } @@ -63,14 +63,14 @@ public abstract class AbstractResourcePatch { // The default patch methods throw an error when no sub-class implementation is provided. protected R add(R restModel, Operation operation) - throws UnprocessableEntityException, PatchBadRequestException { + throws UnprocessableEntityException, DSpaceBadRequestException { throw new UnprocessableEntityException( "The add operation is not supported." ); } protected R replace(R restModel, Operation operation) - throws UnprocessableEntityException, PatchBadRequestException { + throws UnprocessableEntityException, DSpaceBadRequestException { throw new UnprocessableEntityException( "The replace operation is not supported." ); @@ -78,21 +78,21 @@ public abstract class AbstractResourcePatch { protected R remove(R restModel, Operation operation) - throws UnprocessableEntityException, PatchBadRequestException { + throws UnprocessableEntityException, DSpaceBadRequestException { throw new UnprocessableEntityException( "The remove operation is not supported." ); } protected R copy(R restModel, Operation operation) - throws UnprocessableEntityException, PatchBadRequestException { + throws UnprocessableEntityException, DSpaceBadRequestException { throw new UnprocessableEntityException( "The copy operation is not supported." ); } protected R move(R restModel, Operation operation) - throws UnprocessableEntityException, PatchBadRequestException { + throws UnprocessableEntityException, DSpaceBadRequestException { throw new UnprocessableEntityException( "The move operation is not supported." ); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/EPersonPatch.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/EPersonPatch.java index 5c7738eb0e..36abde2fcc 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/EPersonPatch.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/EPersonPatch.java @@ -7,7 +7,7 @@ */ package org.dspace.app.rest.repository.patch; -import org.dspace.app.rest.exception.PatchBadRequestException; +import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.EPersonRest; import org.dspace.app.rest.model.patch.Operation; @@ -32,7 +32,7 @@ public class EPersonPatch extends DSpaceObjectPatch { * @param eperson the eperson rest representation * @param operation the replace operation * @throws UnprocessableEntityException - * @throws PatchBadRequestException + * @throws DSpaceBadRequestException */ protected EPersonRest replace(EPersonRest eperson, Operation operation) { ResourcePatchOperation patchOperation = diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/ItemPatch.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/ItemPatch.java index cc8a331f62..fa652e9616 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/ItemPatch.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/ItemPatch.java @@ -7,7 +7,7 @@ */ package org.dspace.app.rest.repository.patch; -import org.dspace.app.rest.exception.PatchBadRequestException; +import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.model.patch.Operation; @@ -32,7 +32,7 @@ public class ItemPatch extends DSpaceObjectPatch { * @param item the rest representation of the item * @param operation the replace operation * @throws UnprocessableEntityException - * @throws PatchBadRequestException + * @throws DSpaceBadRequestException */ protected ItemRest replace(ItemRest item, Operation operation) { diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/EPersonOperationFactory.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/EPersonOperationFactory.java index c979a4f953..d556634188 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/EPersonOperationFactory.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/EPersonOperationFactory.java @@ -7,7 +7,7 @@ */ package org.dspace.app.rest.repository.patch.factories; -import org.dspace.app.rest.exception.PatchBadRequestException; +import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.model.EPersonRest; import org.dspace.app.rest.repository.patch.factories.impl.EPersonCertificateReplaceOperation; import org.dspace.app.rest.repository.patch.factories.impl.EPersonEmailReplaceOperation; @@ -51,7 +51,7 @@ public class EPersonOperationFactory { * * @param path the operation path * @return the patch operation implementation - * @throws PatchBadRequestException + * @throws DSpaceBadRequestException */ public ResourcePatchOperation getReplaceOperationForPath(String path) { @@ -67,7 +67,7 @@ public class EPersonOperationFactory { case OPERATION_SET_EMAIL: return emailReplaceOperation; default: - throw new PatchBadRequestException("Missing patch operation for: " + path); + throw new DSpaceBadRequestException("Missing patch operation for: " + path); } } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/ItemOperationFactory.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/ItemOperationFactory.java index 3360e85036..8a37ced711 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/ItemOperationFactory.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/ItemOperationFactory.java @@ -7,7 +7,7 @@ */ package org.dspace.app.rest.repository.patch.factories; -import org.dspace.app.rest.exception.PatchBadRequestException; +import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.repository.patch.factories.impl.ItemDiscoverableReplaceOperation; import org.dspace.app.rest.repository.patch.factories.impl.ItemWithdrawReplaceOperation; @@ -37,7 +37,7 @@ public class ItemOperationFactory { * * @param path the operation path * @return the patch operation implementation - * @throws PatchBadRequestException + * @throws DSpaceBadRequestException */ public ResourcePatchOperation getReplaceOperationForPath(String path) { @@ -47,7 +47,7 @@ public class ItemOperationFactory { case OPERATION_PATH_WITHDRAW: return itemWithdrawReplaceOperation; default: - throw new PatchBadRequestException("Missing patch operation for: " + path); + throw new DSpaceBadRequestException("Missing patch operation for: " + path); } } } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/EPersonCertificateReplaceOperation.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/EPersonCertificateReplaceOperation.java index 1ab27d57cc..66e8b117f7 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/EPersonCertificateReplaceOperation.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/EPersonCertificateReplaceOperation.java @@ -7,7 +7,7 @@ */ package org.dspace.app.rest.repository.patch.factories.impl; -import org.dspace.app.rest.exception.PatchBadRequestException; +import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.model.EPersonRest; import org.dspace.app.rest.model.patch.Operation; import org.springframework.stereotype.Component; @@ -42,7 +42,7 @@ public class EPersonCertificateReplaceOperation extends ReplacePatchOperation * @param resource the rest model. * @param operation the patch operation. * @return the updated rest model. - * @throws PatchBadRequestException + * @throws DSpaceBadRequestException */ public abstract R perform(R resource, Operation operation); @@ -37,7 +37,7 @@ public abstract class PatchOperation */ void checkOperationValue(Object value) { if (value == null) { - throw new PatchBadRequestException("No value provided for the operation."); + throw new DSpaceBadRequestException("No value provided for the operation."); } } @@ -46,7 +46,7 @@ public abstract class PatchOperation * * @param value the operation value * @return the original or derived boolean value - * @throws PatchBadRequestException + * @throws DSpaceBadRequestException */ Boolean getBooleanOperationValue(Object value) { Boolean bool; @@ -54,7 +54,8 @@ public abstract class PatchOperation if (value instanceof String) { bool = BooleanUtils.toBooleanObject((String) value); if (bool == null) { - throw new PatchBadRequestException("Boolean value not provided."); + // make sure the string was converted to boolean. + throw new DSpaceBadRequestException("Boolean value not provided."); } } else { bool = (Boolean) value; diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/ReplacePatchOperation.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/ReplacePatchOperation.java index f7c441918b..3205c7019f 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/ReplacePatchOperation.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/ReplacePatchOperation.java @@ -7,7 +7,7 @@ */ package org.dspace.app.rest.repository.patch.factories.impl; -import org.dspace.app.rest.exception.PatchBadRequestException; +import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.RestModel; import org.dspace.app.rest.model.patch.Operation; @@ -29,7 +29,7 @@ public abstract class ReplacePatchOperation * @param resource the rest model. * @param operation the replace patch operation. * @return the updated rest model. - * @throws PatchBadRequestException + * @throws DSpaceBadRequestException * @throws UnprocessableEntityException */ @Override @@ -47,7 +47,7 @@ public abstract class ReplacePatchOperation * @param resource the rest model. * @param operation the replace patch operation. * @return the updated rest model. - * @throws PatchBadRequestException + * @throws DSpaceBadRequestException * @throws UnprocessableEntityException */ abstract R replace(R resource, Operation operation); @@ -59,7 +59,7 @@ public abstract class ReplacePatchOperation * to assure that the replace operation acts only on an existing value. * * @param resource the rest model. - * @throws PatchBadRequestException + * @throws DSpaceBadRequestException */ abstract void checkModelForExistingValue(R resource, Operation operation); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/ResourcePatchOperation.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/ResourcePatchOperation.java index dc531b40fe..9409328fba 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/ResourcePatchOperation.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/ResourcePatchOperation.java @@ -7,7 +7,7 @@ */ package org.dspace.app.rest.repository.patch.factories.impl; -import org.dspace.app.rest.exception.PatchBadRequestException; +import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.model.RestModel; import org.dspace.app.rest.model.patch.Operation; @@ -18,6 +18,6 @@ import org.dspace.app.rest.model.patch.Operation; public interface ResourcePatchOperation { R perform(R resource, Operation operation) - throws PatchBadRequestException; + throws DSpaceBadRequestException; } From d0dcfe26996e144f12ed4de62061ad191551e5db Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Tue, 14 May 2019 09:01:12 +0200 Subject: [PATCH 39/52] [DS-4217] fixed compilation failure after rebase and wrote tests for the DSpaceBadRequestException --- .../impl/EPersonEmailReplaceOperation.java | 4 +- .../app/rest/CollectionRestRepositoryIT.java | 76 +++++++++++++++++++ .../app/rest/CommunityRestRepositoryIT.java | 41 ++++++++++ .../dspace/app/rest/ItemRestRepositoryIT.java | 76 +++++++++++++++++++ 4 files changed, 195 insertions(+), 2 deletions(-) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/EPersonEmailReplaceOperation.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/EPersonEmailReplaceOperation.java index 9651e893a8..9907361e4e 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/EPersonEmailReplaceOperation.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/EPersonEmailReplaceOperation.java @@ -7,7 +7,7 @@ */ package org.dspace.app.rest.repository.patch.factories.impl; -import org.dspace.app.rest.exception.PatchBadRequestException; +import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.model.EPersonRest; import org.dspace.app.rest.model.patch.Operation; import org.springframework.stereotype.Component; @@ -36,7 +36,7 @@ public class EPersonEmailReplaceOperation extends ReplacePatchOperationSome cool HTML code here

")) + .put("dc.description.abstract", + new MetadataValueRest("top-level community created via the REST API")) + .put("dc.description.tableofcontents", + new MetadataValueRest("

HTML News

")) + .put("dc.rights", + new MetadataValueRest("Custom Copyright Text")) + .put("dc.title", + new MetadataValueRest("Title Text"))); + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(post("/api/core/collections") + .content(mapper.writeValueAsBytes(collectionRest)) + .param("parent", "123") + .contentType(contentType)) + .andExpect(status().isBadRequest()); + + } + + + @Test + public void createTestWithoutParentCommunityUUIDBadRequestException() throws Exception { + context.turnOffAuthorisationSystem(); + + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .withLogo("ThisIsSomeDummyText") + .build(); + + ObjectMapper mapper = new ObjectMapper(); + CollectionRest collectionRest = new CollectionRest(); + // We send a name but the created collection should set this to the title + collectionRest.setName("Collection"); + + collectionRest.setMetadata(new MetadataRest() + .put("dc.description", + new MetadataValueRest("

Some cool HTML code here

")) + .put("dc.description.abstract", + new MetadataValueRest("top-level community created via the REST API")) + .put("dc.description.tableofcontents", + new MetadataValueRest("

HTML News

")) + .put("dc.rights", + new MetadataValueRest("Custom Copyright Text")) + .put("dc.title", + new MetadataValueRest("Title Text"))); + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(post("/api/core/collections") + .content(mapper.writeValueAsBytes(collectionRest)) + .contentType(contentType)) + .andExpect(status().isBadRequest()); + + } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java index 3b3c12c720..b83ad7175a 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java @@ -872,4 +872,45 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest new MetadataPatchSuite().runWith(getClient(token), "/api/core/communities/" + parentCommunity.getID(), expectedStatus); } + + @Test + public void createTestInvalidParentCommunityBadRequest() throws Exception { + context.turnOffAuthorisationSystem(); + + ObjectMapper mapper = new ObjectMapper(); + CommunityRest comm = new CommunityRest(); + // We send a name but the created community should set this to the title + comm.setName("Test Top-Level Community"); + + MetadataRest metadataRest = new MetadataRest(); + + MetadataValueRest description = new MetadataValueRest(); + description.setValue("

Some cool HTML code here

"); + metadataRest.put("dc.description", description); + + MetadataValueRest abs = new MetadataValueRest(); + abs.setValue("Sample top-level community created via the REST API"); + metadataRest.put("dc.description.abstract", abs); + + MetadataValueRest contents = new MetadataValueRest(); + contents.setValue("

HTML News

"); + metadataRest.put("dc.description.tableofcontents", contents); + + MetadataValueRest copyright = new MetadataValueRest(); + copyright.setValue("Custom Copyright Text"); + metadataRest.put("dc.rights", copyright); + + MetadataValueRest title = new MetadataValueRest(); + title.setValue("Title Text"); + metadataRest.put("dc.title", title); + + comm.setMetadata(metadataRest); + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(post("/api/core/communities") + .param("parent", "123") + .content(mapper.writeValueAsBytes(comm)) + .contentType(contentType)) + .andExpect(status().isBadRequest()); + } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index 09e4cd1f0f..4aeb428dd4 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -1768,4 +1768,80 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { new MetadataPatchSuite().runWith(getClient(token), "/api/core/items/" + item.getID(), expectedStatus); } + @Test + public void testCreateItemInArchiveFalseBadRequestException() 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(); + + + ObjectMapper mapper = new ObjectMapper(); + ItemRest itemRest = new ItemRest(); + itemRest.setName("Practices of research data curation in institutional repositories:" + + " A qualitative view from repository staff"); + itemRest.setInArchive(false); + itemRest.setDiscoverable(true); + itemRest.setWithdrawn(false); + + itemRest.setMetadata(new MetadataRest() + .put("dc.description", new MetadataValueRest("

Some cool HTML code here

")) + .put("dc.description.abstract", + new MetadataValueRest("Sample item created via the REST API")) + .put("dc.description.tableofcontents", new MetadataValueRest("

HTML News

")) + .put("dc.rights", new MetadataValueRest("Custom Copyright Text")) + .put("dc.title", new MetadataValueRest("Title Text"))); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/core/items?owningCollection=" + col1.getID().toString()) + .content(mapper.writeValueAsBytes(itemRest)).contentType(contentType)) + .andExpect(status().isBadRequest()); + } + + @Test + public void testCreateItemInvalidCollectionBadRequestException() 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(); + + + ObjectMapper mapper = new ObjectMapper(); + ItemRest itemRest = new ItemRest(); + itemRest.setName("Practices of research data curation in institutional repositories:" + + " A qualitative view from repository staff"); + itemRest.setInArchive(false); + itemRest.setDiscoverable(true); + itemRest.setWithdrawn(false); + + itemRest.setMetadata(new MetadataRest() + .put("dc.description", new MetadataValueRest("

Some cool HTML code here

")) + .put("dc.description.abstract", + new MetadataValueRest("Sample item created via the REST API")) + .put("dc.description.tableofcontents", new MetadataValueRest("

HTML News

")) + .put("dc.rights", new MetadataValueRest("Custom Copyright Text")) + .put("dc.title", new MetadataValueRest("Title Text"))); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/core/items?owningCollection=" + parentCommunity.getID().toString()) + .content(mapper.writeValueAsBytes(itemRest)).contentType(contentType)) + .andExpect(status().isBadRequest()); + } + } \ No newline at end of file From 81040975ed67b8a594b2b33a4e003a2090f7e15f Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 21 May 2019 12:15:37 -0500 Subject: [PATCH 40/52] Convert Mock into a Spy of currently loaded bean --- .../src/test/java/org/dspace/app/oai/OAIpmhIT.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/oai/OAIpmhIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/oai/OAIpmhIT.java index 57590f8166..3975d072ce 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/oai/OAIpmhIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/oai/OAIpmhIT.java @@ -40,7 +40,6 @@ import org.dspace.xoai.services.api.xoai.DSpaceFilterResolver; import org.junit.Assume; import org.junit.Before; import org.junit.Test; -import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.SpyBean; @@ -71,13 +70,14 @@ public class OAIpmhIT extends AbstractControllerIntegrationTest { @MockBean private XOAICacheService xoaiCacheService; - // Mock for date-based testing below - @Mock + // Spy on the current EarliestDateResolver bean, to allow us to change behavior in tests below + @SpyBean private EarliestDateResolver earliestDateResolver; + // XOAI's BaseDateProvider (used for date-based testing below) private static BaseDateProvider baseDateProvider = new BaseDateProvider(); - // Spy on the current XOAIManagerResolver, to allow us to change behavior of XOAIManager in tests + // Spy on the current XOAIManagerResolver bean, to allow us to change behavior of XOAIManager in tests // See also: createMockXOAIManager() method @SpyBean private XOAIManagerResolver xoaiManagerResolver; @@ -142,9 +142,10 @@ public class OAIpmhIT extends AbstractControllerIntegrationTest { @Test public void requestForIdentifyShouldReturnTheConfiguredValues() throws Exception { - // Mock EarliestDateResolver to return now for getEarliestDate() + // Get current date/time and store as "now" Date now = new Date(); - when(earliestDateResolver.getEarliestDate(context)).thenReturn(now); + // Return "now" when "getEarliestDate()" is called for the currently loaded EarliestDateResolver bean + doReturn(now).when(earliestDateResolver).getEarliestDate(context); // Attempt to make an Identify request to root context getClient().perform(get(DEFAULT_CONTEXT).param("verb", "Identify")) From 2111d03ffb78027e0861d47d66a2ee9ea7b7114a Mon Sep 17 00:00:00 2001 From: Alexander Sulfrian Date: Fri, 24 May 2019 18:40:48 +0200 Subject: [PATCH 41/52] HAL: Encode username and password in login form --- dspace-spring-rest/src/main/webapp/login.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-spring-rest/src/main/webapp/login.html b/dspace-spring-rest/src/main/webapp/login.html index 8895e7bba2..c7639d0868 100644 --- a/dspace-spring-rest/src/main/webapp/login.html +++ b/dspace-spring-rest/src/main/webapp/login.html @@ -139,7 +139,7 @@ url : window.location.href.replace("login.html", "") + 'api/authn/login', type : 'POST', async : false, - data : 'password='+$("#password").val()+'&user='+$("#username").val() , + data : 'password='+encodeURIComponent($("#password").val())+'&user='+encodeURIComponent($("#username").val()), headers : { "Content-Type" : 'application/x-www-form-urlencoded', "Accept:" : '*/*' From 812b459cf5317e4d9d34d972ab46c1d7f168e73c Mon Sep 17 00:00:00 2001 From: Alexander Sulfrian Date: Fri, 24 May 2019 18:53:10 +0200 Subject: [PATCH 42/52] HAL: Use submit button in login form This enables some automatic features of the browsers, f.e. the form will be submitted when you press the return key in the username or password field. --- dspace-spring-rest/src/main/webapp/login.html | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dspace-spring-rest/src/main/webapp/login.html b/dspace-spring-rest/src/main/webapp/login.html index c7639d0868..448dcf86bf 100644 --- a/dspace-spring-rest/src/main/webapp/login.html +++ b/dspace-spring-rest/src/main/webapp/login.html @@ -55,11 +55,11 @@
-