Refactor Application startup to load DSpace configs early in boot process. Also fixes DS-3492

This commit is contained in:
Tim Donohue
2018-11-28 17:11:08 +00:00
parent 7f3877c7bc
commit fa61b737db
6 changed files with 98 additions and 65 deletions

View File

@@ -14,6 +14,7 @@ import org.dspace.app.rest.filter.DSpaceRequestContextFilter;
import org.dspace.app.rest.model.hateoas.DSpaceRelProvider; import org.dspace.app.rest.model.hateoas.DSpaceRelProvider;
import org.dspace.app.rest.parameter.resolver.SearchFilterResolver; import org.dspace.app.rest.parameter.resolver.SearchFilterResolver;
import org.dspace.app.rest.utils.ApplicationConfig; 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.rest.utils.DSpaceKernelInitializer;
import org.dspace.app.util.DSpaceContextListener; import org.dspace.app.util.DSpaceContextListener;
import org.dspace.utils.servlet.DSpaceWebappServletFilter; import org.dspace.utils.servlet.DSpaceWebappServletFilter;
@@ -22,7 +23,6 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.boot.web.support.SpringBootServletInitializer; import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order; 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 * This is necessary to allow us to build a deployable WAR, rather than
* always relying on embedded Tomcat. * always relying on embedded Tomcat.
* <p> * <p>
* <p>
* See: http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-create-a-deployable-war-file * See: http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-create-a-deployable-war-file
* *
* @param application * @param application
@@ -70,13 +69,10 @@ public class Application extends SpringBootServletInitializer {
*/ */
@Override @Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 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) return application.sources(Application.class)
.initializers(new DSpaceKernelInitializer()); .initializers(new DSpaceKernelInitializer(), new DSpaceConfigurationInitializer());
}
@Bean
public ServletContextInitializer contextInitializer() {
return servletContext -> servletContext.setInitParameter("dspace.dir", configuration.getDspaceHome());
} }
/** /**
@@ -89,7 +85,6 @@ public class Application extends SpringBootServletInitializer {
@Order(2) @Order(2)
protected DSpaceContextListener dspaceContextListener() { protected DSpaceContextListener dspaceContextListener() {
// This listener initializes the DSpace Context object // This listener initializes the DSpace Context object
// (and loads all DSpace configs)
return new DSpaceContextListener(); return new DSpaceContextListener();
} }

View File

@@ -13,27 +13,24 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.data.web.config.EnableSpringDataWebSupport; 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
* <p> * <p>
* NOTE: @ComponentScan on "org.dspace.app.configuration" provides a way for other modules or plugins * NOTE: @ComponentScan on "org.dspace.app.configuration" provides a way for other DSpace modules or plugins
* to "inject" their own configurations / subpaths into our Spring Boot webapp. * to "inject" their own Spring configurations / subpaths into our Spring Boot webapp.
*
* @author Andrea Bollini (andrea.bollini at 4science.it) * @author Andrea Bollini (andrea.bollini at 4science.it)
* @author Tim Donohue
*/ */
@Configuration @Configuration
@EnableSpringDataWebSupport @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"}) "org.dspace.app.configuration"})
public class ApplicationConfig { public class ApplicationConfig {
@Value("${dspace.dir}") // Allowed CORS origins. Defaults to * (everywhere)
private String dspaceHome; // Can be overridden in DSpace configuration
@Value("${rest.cors.allowed-origins:*}")
@Value("${cors.allowed-origins}")
private String corsAllowedOrigins; private String corsAllowedOrigins;
public String getDspaceHome() {
return dspaceHome;
}
public String[] getCorsAllowedOrigins() { public String[] getCorsAllowedOrigins() {
if (corsAllowedOrigins != null) { if (corsAllowedOrigins != null) {
return corsAllowedOrigins.split("\\s*,\\s*"); return corsAllowedOrigins.split("\\s*,\\s*");

View File

@@ -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.
* <P>
* NOTE: MUST be loaded after DSpaceKernelInitializer, as it requires the kernel is already initialized.
* <P>
* 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.
* <P>
* Used by org.dspace.app.rest.Application
*/
public class DSpaceConfigurationInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
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);
}
}

View File

@@ -11,6 +11,7 @@ import java.io.File;
import javax.naming.Context; import javax.naming.Context;
import javax.naming.InitialContext; import javax.naming.InitialContext;
import org.apache.commons.lang3.StringUtils;
import org.dspace.kernel.DSpaceKernel; import org.dspace.kernel.DSpaceKernel;
import org.dspace.kernel.DSpaceKernelManager; import org.dspace.kernel.DSpaceKernelManager;
import org.dspace.servicemanager.DSpaceKernelImpl; import org.dspace.servicemanager.DSpaceKernelImpl;
@@ -22,6 +23,7 @@ import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextClosedEvent; import org.springframework.context.event.ContextClosedEvent;
import org.springframework.core.env.ConfigurableEnvironment;
/** /**
* Utility class that will initialize the DSpace Kernel on Spring Boot startup. * Utility class that will initialize the DSpace Kernel on Spring Boot startup.
@@ -35,16 +37,16 @@ public class DSpaceKernelInitializer implements ApplicationContextInitializer<Co
@Override @Override
public void initialize(final ConfigurableApplicationContext applicationContext) { public void initialize(final ConfigurableApplicationContext applicationContext) {
// Check if the kernel is already started
String dspaceHome = applicationContext.getEnvironment().getProperty("dspace.dir");
this.dspaceKernel = DSpaceKernelManager.getDefaultKernel(); this.dspaceKernel = DSpaceKernelManager.getDefaultKernel();
if (this.dspaceKernel == null) { if (this.dspaceKernel == null) {
DSpaceKernelImpl kernelImpl = null; DSpaceKernelImpl kernelImpl = null;
try { try {
// Load the kernel with default settings
kernelImpl = DSpaceKernelInit.getKernel(null); kernelImpl = DSpaceKernelInit.getKernel(null);
if (!kernelImpl.isRunning()) { if (!kernelImpl.isRunning()) {
kernelImpl.start(getProvidedHome(dspaceHome)); // init the kernel // Determine configured DSpace home & init the Kernel
kernelImpl.start(getDSpaceHome(applicationContext.getEnvironment()));
} }
this.dspaceKernel = kernelImpl; this.dspaceKernel = kernelImpl;
@@ -65,8 +67,8 @@ public class DSpaceKernelInitializer implements ApplicationContextInitializer<Co
} }
if (applicationContext.getParent() == null) { if (applicationContext.getParent() == null) {
//Set the DSpace Kernel Application context as a parent of the Spring Boot context so that // Set the DSpace Kernel Application context as a parent of the Spring Boot context so that
//we can auto-wire all DSpace Kernel services // we can auto-wire all DSpace Kernel services
applicationContext.setParent(dspaceKernel.getServiceManager().getApplicationContext()); applicationContext.setParent(dspaceKernel.getServiceManager().getApplicationContext());
//Add a listener for Spring Boot application shutdown so that we can nicely cleanup the DSpace kernel. //Add a listener for Spring Boot application shutdown so that we can nicely cleanup the DSpace kernel.
@@ -75,29 +77,35 @@ public class DSpaceKernelInitializer implements ApplicationContextInitializer<Co
} }
/** /**
* Find DSpace's "home" directory. * Find DSpace's "home" directory (from current environment)
* Initially look for JNDI Resource called "java:/comp/env/dspace.dir". * Initially look for JNDI Resource called "java:/comp/env/dspace.dir".
* If not found, look for "dspace.dir" initial context parameter. * If not found, use value provided in "dspace.dir" in Spring Environment
*/ */
private String getProvidedHome(String dspaceHome) { private String getDSpaceHome(ConfigurableEnvironment environment) {
String providedHome = null; // Load the "dspace.dir" property from Spring Boot's Configuration (application.properties)
// This gives us the location of our DSpace configurations, necessary to start the kernel
String providedHome = environment.getProperty(DSpaceConfigurationService.DSPACE_HOME);
String dspaceHome = null;
try { try {
// Allow ability to override home directory via JNDI
Context ctx = new InitialContext(); Context ctx = new InitialContext();
providedHome = (String) ctx.lookup("java:/comp/env/" + DSpaceConfigurationService.DSPACE_HOME); dspaceHome = (String) ctx.lookup("java:/comp/env/" + DSpaceConfigurationService.DSPACE_HOME);
} catch (Exception e) { } catch (Exception e) {
// do nothing // do nothing
} }
if (providedHome == null) { // Otherwise, verify the 'providedHome' value is non-empty, exists and includes DSpace configs
if (dspaceHome != null && !dspaceHome.equals("") && if (dspaceHome == null) {
!dspaceHome.equals("${" + DSpaceConfigurationService.DSPACE_HOME + "}")) { if (StringUtils.isNotBlank(providedHome) &&
File test = new File(dspaceHome); !providedHome.equals("${" + DSpaceConfigurationService.DSPACE_HOME + "}")) {
File test = new File(providedHome);
if (test.exists() && new File(test, DSpaceConfigurationService.DSPACE_CONFIG_PATH).exists()) { if (test.exists() && new File(test, DSpaceConfigurationService.DSPACE_CONFIG_PATH).exists()) {
providedHome = dspaceHome; dspaceHome = providedHome;
} }
} }
} }
return providedHome; return dspaceHome;
} }

View File

@@ -13,28 +13,14 @@
# For common settings see: # For common settings see:
# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html # http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
# #
#
# TODO: Eventually would could think of "wiring" this up to use Commons Configuration as well
# See, for example: http://stackoverflow.com/questions/25271537/remote-propertysource
# and https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html
######################## ########################
# DSpace Settings # DSpace Settings
# #
# DSpace installation directory # 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()
dspace.dir=${dspace.dir} dspace.dir=${dspace.dir}
#dspace.dir=d:/install/dspace7
########################
# DSpace API CORS Settings
#
cors.allowed-origins = *
########################
# Spring Boot Settings
#
# Testing an "application Name"
spring.application.name = DSpace Spring Rest
######################## ########################
# Spring DATA Rest settings # Spring DATA Rest settings
@@ -75,15 +61,6 @@ server.port=8080
# (Optional, defaults to root context) # (Optional, defaults to root context)
#server.context-path=/spring-data-rest #server.context-path=/spring-data-rest
# This creates a Tomcat context-param named "dspace.dir"
# and sets it to the value of the "dspace.dir" property (listed above)
server.context-parameters.dspace.dir=${dspace.dir}
# This creates a Tomcat context-param named "dspace-config"
# (Used by DSpaceContextListener to load the configurations)
# This is only needed in DSpace 5 or below to initialize ConfigurationManager
#server.context-parameters.dspace-config=${dspace.dir}/config/dspace.cfg
# Error handling settings # Error handling settings
# Always include the fullstacktrace in error pages # Always include the fullstacktrace in error pages
# Can be set to "never" if you don't want it. # Can be set to "never" if you don't want it.

View File

@@ -1,9 +1,17 @@
#---------------------------------------------------------------# #---------------------------------------------------------------#
#--------------------REST CONFIGURATIONS------------------------# #--------------------REST CONFIGURATIONS------------------------#
#---------------------------------------------------------------# #---------------------------------------------------------------#
# These configs are used by the REST module # # These configs are used by the RESTv7 module #
#---------------------------------------------------------------# #---------------------------------------------------------------#
# Allowed CORS origins. Defaults to * (everywhere)
# Multiple allowed origin URLs may be comma separated
# (Requires reboot of servlet container, e.g. Tomcat, to reload)
rest.cors.allowed-origins = *
#---------------------------------------------------------------#
# These configs are used by the deprecated REST (v4-6) module #
#---------------------------------------------------------------#
# record stats in DSpace statistics module # record stats in DSpace statistics module
rest.stats = true rest.stats = true
@@ -137,4 +145,4 @@ rest.report-regex-xml-entity = ^.*&#.*$
rest.report-regex-non-ascii = ^.*[^\\p{ASCII}].*$ rest.report-regex-non-ascii = ^.*[^\\p{ASCII}].*$
# The maximum number of results to return for 1 request # The maximum number of results to return for 1 request
rest.search.max.results = 100 rest.search.max.results = 100