mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-14 09:25:43 +01:00
simplify scm-manager bootstrap
We have simplified the scm-manager bootstrap process, by replacing the ServletContextListener call hierarchy with much simpler ModuleProviders. We have also removed the parent injector. Now we create always a new injector. If something goes wrong in the process of injector creation, we will show a nicely styled error page instead of stacktrace on a white page.
This commit is contained in:
@@ -0,0 +1,70 @@
|
|||||||
|
package sonia.scm;
|
||||||
|
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
import org.jboss.resteasy.plugins.guice.ModuleProcessor;
|
||||||
|
import org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher;
|
||||||
|
import org.jboss.resteasy.plugins.server.servlet.ListenerBootstrap;
|
||||||
|
import org.jboss.resteasy.spi.Registry;
|
||||||
|
import org.jboss.resteasy.spi.ResteasyDeployment;
|
||||||
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import javax.servlet.ServletConfig;
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resteasy initialization and dispatching. This servlet combines the initialization of
|
||||||
|
* {@link org.jboss.resteasy.plugins.guice.GuiceResteasyBootstrapServletContextListener} and the dispatching of
|
||||||
|
* {@link HttpServletDispatcher}. The combination is required to fix the initialization order.
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class ResteasyAllInOneServletDispatcher extends HttpServletDispatcher {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ResteasyAllInOneServletDispatcher.class);
|
||||||
|
|
||||||
|
private final Injector injector;
|
||||||
|
private ResteasyDeployment deployment;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ResteasyAllInOneServletDispatcher(Injector injector) {
|
||||||
|
this.injector = injector;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(ServletConfig servletConfig) throws ServletException {
|
||||||
|
LOG.info("init resteasy");
|
||||||
|
|
||||||
|
ServletContext servletContext = servletConfig.getServletContext();
|
||||||
|
createDeployment(servletContext);
|
||||||
|
|
||||||
|
ModuleProcessor processor = createModuleProcessor();
|
||||||
|
processor.processInjector(injector);
|
||||||
|
|
||||||
|
super.init(servletConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createDeployment(ServletContext servletContext) {
|
||||||
|
ListenerBootstrap config = new ListenerBootstrap(servletContext);
|
||||||
|
deployment = config.createDeployment();
|
||||||
|
deployment.start();
|
||||||
|
|
||||||
|
servletContext.setAttribute(ResteasyDeployment.class.getName(), deployment);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ModuleProcessor createModuleProcessor() {
|
||||||
|
Registry registry = deployment.getRegistry();
|
||||||
|
ResteasyProviderFactory providerFactory = deployment.getProviderFactory();
|
||||||
|
return new ModuleProcessor(registry, providerFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
LOG.info("destroy resteasy");
|
||||||
|
super.destroy();
|
||||||
|
deployment.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,19 +2,17 @@ package sonia.scm;
|
|||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.inject.servlet.ServletModule;
|
import com.google.inject.servlet.ServletModule;
|
||||||
import org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher;
|
|
||||||
import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters;
|
import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters;
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module to configure resteasy with guice.
|
||||||
|
*/
|
||||||
public class ResteasyModule extends ServletModule {
|
public class ResteasyModule extends ServletModule {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void configureServlets() {
|
protected void configureServlets() {
|
||||||
bind(HttpServletDispatcher.class).in(Singleton.class);
|
|
||||||
|
|
||||||
Map<String, String> initParams = ImmutableMap.of(ResteasyContextParameters.RESTEASY_SERVLET_MAPPING_PREFIX, "/api");
|
Map<String, String> initParams = ImmutableMap.of(ResteasyContextParameters.RESTEASY_SERVLET_MAPPING_PREFIX, "/api");
|
||||||
serve("/api/*").with(HttpServletDispatcher.class, initParams);
|
serve("/api/*").with(ResteasyAllInOneServletDispatcher.class, initParams);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,183 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010, Sebastian Sdorra
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from this
|
|
||||||
* software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package sonia.scm;
|
|
||||||
|
|
||||||
import com.google.common.base.Throwables;
|
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import com.google.inject.Injector;
|
|
||||||
import com.google.inject.Module;
|
|
||||||
import com.google.inject.assistedinject.Assisted;
|
|
||||||
import org.apache.shiro.guice.web.ShiroWebModule;
|
|
||||||
import org.jboss.resteasy.plugins.guice.GuiceResteasyBootstrapServletContextListener;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import sonia.scm.api.v2.resources.MapperModule;
|
|
||||||
import sonia.scm.cache.CacheManager;
|
|
||||||
import sonia.scm.debug.DebugModule;
|
|
||||||
import sonia.scm.filter.WebElementModule;
|
|
||||||
import sonia.scm.group.GroupManager;
|
|
||||||
import sonia.scm.plugin.DefaultPluginLoader;
|
|
||||||
import sonia.scm.plugin.ExtensionProcessor;
|
|
||||||
import sonia.scm.plugin.PluginWrapper;
|
|
||||||
import sonia.scm.repository.RepositoryManager;
|
|
||||||
import sonia.scm.schedule.Scheduler;
|
|
||||||
import sonia.scm.user.UserManager;
|
|
||||||
import sonia.scm.util.IOUtil;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.servlet.ServletContext;
|
|
||||||
import javax.servlet.ServletContextEvent;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
public class ScmContextListener extends GuiceResteasyBootstrapServletContextListener
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the logger for ScmContextListener
|
|
||||||
*/
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ScmContextListener.class);
|
|
||||||
|
|
||||||
private final ClassLoader parent;
|
|
||||||
private final Set<PluginWrapper> plugins;
|
|
||||||
private Injector injector;
|
|
||||||
|
|
||||||
public interface Factory {
|
|
||||||
ScmContextListener create(ClassLoader parent, Set<PluginWrapper> plugins);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public ScmContextListener(@Assisted ClassLoader parent, @Assisted Set<PluginWrapper> plugins)
|
|
||||||
{
|
|
||||||
this.parent = parent;
|
|
||||||
this.plugins = plugins;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<PluginWrapper> getPlugins() {
|
|
||||||
return plugins;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void contextInitialized(ServletContextEvent servletContextEvent) {
|
|
||||||
beforeInjectorCreation();
|
|
||||||
super.contextInitialized(servletContextEvent);
|
|
||||||
afterInjectorCreation(servletContextEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void beforeInjectorCreation() {
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasStartupErrors() {
|
|
||||||
return SCMContext.getContext().getStartupError() != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<? extends Module> getModules(ServletContext context) {
|
|
||||||
DefaultPluginLoader pluginLoader = new DefaultPluginLoader(context, parent, plugins);
|
|
||||||
|
|
||||||
ClassOverrides overrides = ClassOverrides.findOverrides(pluginLoader.getUberClassLoader());
|
|
||||||
List<Module> moduleList = Lists.newArrayList();
|
|
||||||
|
|
||||||
moduleList.add(new ResteasyModule());
|
|
||||||
moduleList.add(ShiroWebModule.guiceFilterModule());
|
|
||||||
moduleList.add(new WebElementModule(pluginLoader));
|
|
||||||
moduleList.add(new ScmServletModule(context, pluginLoader, overrides));
|
|
||||||
moduleList.add(
|
|
||||||
new ScmSecurityModule(context, pluginLoader.getExtensionProcessor())
|
|
||||||
);
|
|
||||||
appendModules(pluginLoader.getExtensionProcessor(), moduleList);
|
|
||||||
moduleList.addAll(overrides.getModules());
|
|
||||||
|
|
||||||
if (SCMContext.getContext().getStage() == Stage.DEVELOPMENT){
|
|
||||||
moduleList.add(new DebugModule());
|
|
||||||
}
|
|
||||||
moduleList.add(new MapperModule());
|
|
||||||
|
|
||||||
return moduleList;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void appendModules(ExtensionProcessor ep, List<Module> moduleList) {
|
|
||||||
for (Class<? extends Module> module : ep.byExtensionPoint(Module.class)) {
|
|
||||||
try {
|
|
||||||
LOG.info("add module {}", module);
|
|
||||||
moduleList.add(module.newInstance());
|
|
||||||
} catch (IllegalAccessException | InstantiationException ex) {
|
|
||||||
throw Throwables.propagate(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void withInjector(Injector injector) {
|
|
||||||
this.injector = injector;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void afterInjectorCreation(ServletContextEvent event) {
|
|
||||||
if (injector != null && !hasStartupErrors()) {
|
|
||||||
bindEagerSingletons();
|
|
||||||
initializeServletContextListeners(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void bindEagerSingletons() {
|
|
||||||
injector.getInstance(EagerSingletonModule.class).initialize(injector);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeServletContextListeners(ServletContextEvent event) {
|
|
||||||
injector.getInstance(ServletContextListenerHolder.class).contextInitialized(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void contextDestroyed(ServletContextEvent servletContextEvent)
|
|
||||||
{
|
|
||||||
if (injector != null &&!hasStartupErrors()) {
|
|
||||||
closeCloseables();
|
|
||||||
destroyServletContextListeners(servletContextEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
super.contextDestroyed(servletContextEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void closeCloseables() {
|
|
||||||
injector.getInstance(CloseableModule.class).closeAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void destroyServletContextListeners(ServletContextEvent event) {
|
|
||||||
injector.getInstance(ServletContextListenerHolder.class).contextDestroyed(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -81,8 +81,7 @@ public class ScmSecurityModule extends ShiroWebModule
|
|||||||
* @param servletContext
|
* @param servletContext
|
||||||
* @param extensionProcessor
|
* @param extensionProcessor
|
||||||
*/
|
*/
|
||||||
ScmSecurityModule(ServletContext servletContext,
|
public ScmSecurityModule(ServletContext servletContext, ExtensionProcessor extensionProcessor)
|
||||||
ExtensionProcessor extensionProcessor)
|
|
||||||
{
|
{
|
||||||
super(servletContext);
|
super(servletContext);
|
||||||
this.extensionProcessor = extensionProcessor;
|
this.extensionProcessor = extensionProcessor;
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ import sonia.scm.net.ahc.ContentTransformer;
|
|||||||
import sonia.scm.net.ahc.DefaultAdvancedHttpClient;
|
import sonia.scm.net.ahc.DefaultAdvancedHttpClient;
|
||||||
import sonia.scm.net.ahc.JsonContentTransformer;
|
import sonia.scm.net.ahc.JsonContentTransformer;
|
||||||
import sonia.scm.net.ahc.XmlContentTransformer;
|
import sonia.scm.net.ahc.XmlContentTransformer;
|
||||||
import sonia.scm.plugin.DefaultPluginLoader;
|
|
||||||
import sonia.scm.plugin.DefaultPluginManager;
|
import sonia.scm.plugin.DefaultPluginManager;
|
||||||
|
import sonia.scm.plugin.PluginLoader;
|
||||||
import sonia.scm.plugin.PluginManager;
|
import sonia.scm.plugin.PluginManager;
|
||||||
import sonia.scm.repository.DefaultRepositoryManager;
|
import sonia.scm.repository.DefaultRepositoryManager;
|
||||||
import sonia.scm.repository.DefaultRepositoryProvider;
|
import sonia.scm.repository.DefaultRepositoryProvider;
|
||||||
@@ -110,7 +110,6 @@ import sonia.scm.web.security.AdministrationContext;
|
|||||||
import sonia.scm.web.security.DefaultAdministrationContext;
|
import sonia.scm.web.security.DefaultAdministrationContext;
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
import javax.servlet.ServletContext;
|
|
||||||
|
|
||||||
import static sonia.scm.api.v2.resources.ScmPathInfo.REST_API_PATH;
|
import static sonia.scm.api.v2.resources.ScmPathInfo.REST_API_PATH;
|
||||||
|
|
||||||
@@ -179,9 +178,8 @@ public class ScmServletModule extends ServletModule
|
|||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
//~--- constructors ---------------------------------------------------------
|
||||||
|
|
||||||
ScmServletModule(ServletContext servletContext, DefaultPluginLoader pluginLoader, ClassOverrides overrides)
|
public ScmServletModule(PluginLoader pluginLoader, ClassOverrides overrides)
|
||||||
{
|
{
|
||||||
this.servletContext = servletContext;
|
|
||||||
this.pluginLoader = pluginLoader;
|
this.pluginLoader = pluginLoader;
|
||||||
this.overrides = overrides;
|
this.overrides = overrides;
|
||||||
}
|
}
|
||||||
@@ -206,10 +204,6 @@ public class ScmServletModule extends ServletModule
|
|||||||
RepositoryProvider.class, Repository.class).to(
|
RepositoryProvider.class, Repository.class).to(
|
||||||
DefaultRepositoryProvider.class).in(RequestScoped.class);
|
DefaultRepositoryProvider.class).in(RequestScoped.class);
|
||||||
|
|
||||||
// bind servlet context
|
|
||||||
bind(ServletContext.class).annotatedWith(Default.class).toInstance(
|
|
||||||
servletContext);
|
|
||||||
|
|
||||||
// bind event api
|
// bind event api
|
||||||
bind(ScmEventBus.class).toInstance(ScmEventBus.getInstance());
|
bind(ScmEventBus.class).toInstance(ScmEventBus.getInstance());
|
||||||
|
|
||||||
@@ -408,8 +402,5 @@ public class ScmServletModule extends ServletModule
|
|||||||
private final ClassOverrides overrides;
|
private final ClassOverrides overrides;
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private final DefaultPluginLoader pluginLoader;
|
private final PluginLoader pluginLoader;
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private final ServletContext servletContext;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package sonia.scm.boot;
|
||||||
|
|
||||||
|
import com.google.common.base.Throwables;
|
||||||
|
import com.google.inject.Module;
|
||||||
|
import org.apache.shiro.guice.web.ShiroWebModule;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.ClassOverrides;
|
||||||
|
import sonia.scm.ResteasyModule;
|
||||||
|
import sonia.scm.SCMContext;
|
||||||
|
import sonia.scm.ScmSecurityModule;
|
||||||
|
import sonia.scm.ScmServletModule;
|
||||||
|
import sonia.scm.Stage;
|
||||||
|
import sonia.scm.api.v2.resources.MapperModule;
|
||||||
|
import sonia.scm.debug.DebugModule;
|
||||||
|
import sonia.scm.filter.WebElementModule;
|
||||||
|
import sonia.scm.plugin.ExtensionProcessor;
|
||||||
|
import sonia.scm.plugin.PluginLoader;
|
||||||
|
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
class ApplicationModuleProvider implements ModuleProvider {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ApplicationModuleProvider.class);
|
||||||
|
|
||||||
|
private final ServletContext servletContext;
|
||||||
|
private final PluginLoader pluginLoader;
|
||||||
|
|
||||||
|
ApplicationModuleProvider(ServletContext servletContext, PluginLoader pluginLoader) {
|
||||||
|
this.servletContext = servletContext;
|
||||||
|
this.pluginLoader = pluginLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Module> createModules() {
|
||||||
|
ClassOverrides overrides = createClassOverrides();
|
||||||
|
return createModules(overrides);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Module> createModules(ClassOverrides overrides) {
|
||||||
|
List<Module> moduleList = new ArrayList<>();
|
||||||
|
moduleList.add(new ResteasyModule());
|
||||||
|
moduleList.add(ShiroWebModule.guiceFilterModule());
|
||||||
|
moduleList.add(new WebElementModule(pluginLoader));
|
||||||
|
moduleList.add(new ScmServletModule(pluginLoader, overrides));
|
||||||
|
moduleList.add(
|
||||||
|
new ScmSecurityModule(servletContext, pluginLoader.getExtensionProcessor())
|
||||||
|
);
|
||||||
|
appendModules(pluginLoader.getExtensionProcessor(), moduleList);
|
||||||
|
moduleList.addAll(overrides.getModules());
|
||||||
|
|
||||||
|
if (SCMContext.getContext().getStage() == Stage.DEVELOPMENT){
|
||||||
|
moduleList.add(new DebugModule());
|
||||||
|
}
|
||||||
|
moduleList.add(new MapperModule());
|
||||||
|
|
||||||
|
return moduleList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClassOverrides createClassOverrides() {
|
||||||
|
ClassLoader uberClassLoader = pluginLoader.getUberClassLoader();
|
||||||
|
return ClassOverrides.findOverrides(uberClassLoader);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendModules(ExtensionProcessor ep, List<Module> moduleList) {
|
||||||
|
for (Class<? extends Module> module : ep.byExtensionPoint(Module.class)) {
|
||||||
|
try {
|
||||||
|
LOG.info("add module {}", module);
|
||||||
|
moduleList.add(module.newInstance());
|
||||||
|
} catch (IllegalAccessException | InstantiationException ex) {
|
||||||
|
throw Throwables.propagate(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -82,7 +82,6 @@ public class BootstrapContextFilter extends GuiceFilter {
|
|||||||
super.destroy();
|
super.destroy();
|
||||||
|
|
||||||
listener.contextDestroyed(new ServletContextEvent(filterConfig.getServletContext()));
|
listener.contextDestroyed(new ServletContextEvent(filterConfig.getServletContext()));
|
||||||
ServletContextCleaner.cleanup(filterConfig.getServletContext());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -29,111 +29,134 @@
|
|||||||
|
|
||||||
package sonia.scm.boot;
|
package sonia.scm.boot;
|
||||||
|
|
||||||
import com.google.inject.AbstractModule;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.inject.Guice;
|
import com.google.inject.Guice;
|
||||||
import com.google.inject.Injector;
|
import com.google.inject.Injector;
|
||||||
import com.google.inject.Module;
|
import com.google.inject.Module;
|
||||||
import com.google.inject.assistedinject.FactoryModuleBuilder;
|
import com.google.inject.servlet.GuiceServletContextListener;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.CloseableModule;
|
import sonia.scm.CloseableModule;
|
||||||
import sonia.scm.EagerSingletonModule;
|
import sonia.scm.EagerSingletonModule;
|
||||||
import sonia.scm.SCMContext;
|
import sonia.scm.SCMContext;
|
||||||
import sonia.scm.ScmContextListener;
|
|
||||||
import sonia.scm.ScmEventBusModule;
|
import sonia.scm.ScmEventBusModule;
|
||||||
import sonia.scm.ScmInitializerModule;
|
import sonia.scm.ScmInitializerModule;
|
||||||
import sonia.scm.plugin.PluginLoader;
|
import sonia.scm.plugin.PluginLoader;
|
||||||
import sonia.scm.plugin.PluginWrapper;
|
import sonia.scm.update.MigrationWizardModuleProvider;
|
||||||
import sonia.scm.update.MigrationWizardContextListener;
|
|
||||||
import sonia.scm.update.UpdateEngine;
|
import sonia.scm.update.UpdateEngine;
|
||||||
|
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
import javax.servlet.ServletContextEvent;
|
import javax.servlet.ServletContextEvent;
|
||||||
import javax.servlet.ServletContextListener;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.util.Set;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
public class BootstrapContextListener implements ServletContextListener {
|
public class BootstrapContextListener extends GuiceServletContextListener {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(BootstrapContextListener.class);
|
||||||
|
|
||||||
private final ClassLoaderLifeCycle classLoaderLifeCycle = ClassLoaderLifeCycle.create();
|
private final ClassLoaderLifeCycle classLoaderLifeCycle = ClassLoaderLifeCycle.create();
|
||||||
|
|
||||||
|
|
||||||
private ServletContext context;
|
private ServletContext context;
|
||||||
private ServletContextListener contextListener;
|
private InjectionLifeCycle injectionLifeCycle;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void contextInitialized(ServletContextEvent sce) {
|
public void contextInitialized(ServletContextEvent sce) {
|
||||||
classLoaderLifeCycle.init();
|
LOG.info("start scm-manager initialization");
|
||||||
|
|
||||||
context = sce.getServletContext();
|
context = sce.getServletContext();
|
||||||
|
classLoaderLifeCycle.init();
|
||||||
|
super.contextInitialized(sce);
|
||||||
|
|
||||||
createContextListener();
|
Injector injector = (Injector) context.getAttribute(Injector.class.getName());
|
||||||
|
injectionLifeCycle = new InjectionLifeCycle(injector);
|
||||||
|
injectionLifeCycle.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
contextListener.contextInitialized(sce);
|
@Override
|
||||||
|
protected Injector getInjector() {
|
||||||
|
Throwable startupError = SCMContext.getContext().getStartupError();
|
||||||
|
if (startupError != null) {
|
||||||
|
return createStageOneInjector(SingleView.error(startupError));
|
||||||
|
} else if (Versions.isTooOld()) {
|
||||||
|
return createStageOneInjector(SingleView.view("/templates/too-old.mustache", HttpServletResponse.SC_CONFLICT));
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
return createStageTwoInjector();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return createStageOneInjector(SingleView.error(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void contextDestroyed(ServletContextEvent sce) {
|
public void contextDestroyed(ServletContextEvent sce) {
|
||||||
contextListener.contextDestroyed(sce);
|
LOG.info("shutdown scm-manager context");
|
||||||
|
|
||||||
|
ServletContextCleaner.cleanup(context);
|
||||||
|
|
||||||
|
injectionLifeCycle.shutdown();
|
||||||
|
injectionLifeCycle = null;
|
||||||
classLoaderLifeCycle.shutdown();
|
classLoaderLifeCycle.shutdown();
|
||||||
|
|
||||||
context = null;
|
|
||||||
contextListener = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createContextListener() {
|
private Injector createStageTwoInjector() {
|
||||||
Throwable startupError = SCMContext.getContext().getStartupError();
|
|
||||||
if (startupError != null) {
|
|
||||||
contextListener = SingleView.error(startupError);
|
|
||||||
} else if (Versions.isTooOld()) {
|
|
||||||
contextListener = SingleView.view("/templates/too-old.mustache", HttpServletResponse.SC_CONFLICT);
|
|
||||||
} else {
|
|
||||||
createMigrationOrNormalContextListener();
|
|
||||||
Versions.writeNew();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createMigrationOrNormalContextListener() {
|
|
||||||
PluginBootstrap pluginBootstrap = new PluginBootstrap(context, classLoaderLifeCycle);
|
PluginBootstrap pluginBootstrap = new PluginBootstrap(context, classLoaderLifeCycle);
|
||||||
|
|
||||||
|
ModuleProvider provider = createMigrationOrNormalModuleProvider(pluginBootstrap);
|
||||||
|
return createStageTwoInjector(provider, pluginBootstrap.getPluginLoader());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ModuleProvider createMigrationOrNormalModuleProvider(PluginBootstrap pluginBootstrap) {
|
||||||
Injector bootstrapInjector = createBootstrapInjector(pluginBootstrap.getPluginLoader());
|
Injector bootstrapInjector = createBootstrapInjector(pluginBootstrap.getPluginLoader());
|
||||||
|
|
||||||
startEitherMigrationOrNormalServlet(classLoaderLifeCycle.getBootstrapClassLoader(), pluginBootstrap.getPlugins(), pluginBootstrap.getPluginLoader(), bootstrapInjector);
|
return startEitherMigrationOrApplication(pluginBootstrap.getPluginLoader(), bootstrapInjector);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startEitherMigrationOrNormalServlet(ClassLoader cl, Set<PluginWrapper> plugins, PluginLoader pluginLoader, Injector bootstrapInjector) {
|
private ModuleProvider startEitherMigrationOrApplication(PluginLoader pluginLoader, Injector bootstrapInjector) {
|
||||||
MigrationWizardContextListener wizardContextListener = prepareWizardIfNeeded(bootstrapInjector);
|
MigrationWizardModuleProvider wizardModuleProvider = new MigrationWizardModuleProvider(bootstrapInjector);
|
||||||
|
|
||||||
if (wizardContextListener.wizardNecessary()) {
|
if (wizardModuleProvider.wizardNecessary()) {
|
||||||
contextListener = wizardContextListener;
|
return wizardModuleProvider;
|
||||||
} else {
|
} else {
|
||||||
processUpdates(pluginLoader, bootstrapInjector);
|
processUpdates(pluginLoader, bootstrapInjector);
|
||||||
contextListener = bootstrapInjector.getInstance(ScmContextListener.Factory.class).create(cl, plugins);
|
|
||||||
|
Versions.writeNew();
|
||||||
|
|
||||||
|
return new ApplicationModuleProvider(context, pluginLoader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Injector createStageOneInjector(ModuleProvider provider) {
|
||||||
|
return Guice.createInjector(provider.createModules());
|
||||||
|
}
|
||||||
|
|
||||||
private MigrationWizardContextListener prepareWizardIfNeeded(Injector bootstrapInjector) {
|
private Injector createStageTwoInjector(ModuleProvider provider, PluginLoader pluginLoader) {
|
||||||
return new MigrationWizardContextListener(bootstrapInjector);
|
List<Module> modules = new ArrayList<>(createBootstrapModules(pluginLoader));
|
||||||
|
modules.addAll(provider.createModules());
|
||||||
|
return Guice.createInjector(modules);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Injector createBootstrapInjector(PluginLoader pluginLoader) {
|
private Injector createBootstrapInjector(PluginLoader pluginLoader) {
|
||||||
Module scmContextListenerModule = new ScmContextListenerModule();
|
return Guice.createInjector(createBootstrapModules(pluginLoader));
|
||||||
BootstrapModule bootstrapModule = new BootstrapModule(pluginLoader);
|
}
|
||||||
ScmInitializerModule scmInitializerModule = new ScmInitializerModule();
|
|
||||||
EagerSingletonModule eagerSingletonModule = new EagerSingletonModule();
|
|
||||||
CloseableModule closeableModule = new CloseableModule();
|
|
||||||
ScmEventBusModule scmEventBusModule = new ScmEventBusModule();
|
|
||||||
|
|
||||||
return Guice.createInjector(
|
private List<Module> createBootstrapModules(PluginLoader pluginLoader) {
|
||||||
bootstrapModule,
|
List<Module> modules = new ArrayList<>(createBaseModules());
|
||||||
scmContextListenerModule,
|
modules.add(new BootstrapModule(pluginLoader));
|
||||||
scmEventBusModule,
|
return modules;
|
||||||
scmInitializerModule,
|
}
|
||||||
eagerSingletonModule,
|
|
||||||
closeableModule
|
private List<Module> createBaseModules() {
|
||||||
|
return ImmutableList.of(
|
||||||
|
new EagerSingletonModule(),
|
||||||
|
new ScmInitializerModule(),
|
||||||
|
new ScmEventBusModule(),
|
||||||
|
new ServletContextModule(),
|
||||||
|
new CloseableModule()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,10 +167,4 @@ public class BootstrapContextListener implements ServletContextListener {
|
|||||||
updateEngine.update();
|
updateEngine.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ScmContextListenerModule extends AbstractModule {
|
|
||||||
@Override
|
|
||||||
protected void configure() {
|
|
||||||
install(new FactoryModuleBuilder().build(ScmContextListener.Factory.class));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package sonia.scm.boot;
|
||||||
|
|
||||||
|
import com.google.inject.Binding;
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
import com.google.inject.Key;
|
||||||
|
import sonia.scm.CloseableModule;
|
||||||
|
import sonia.scm.Default;
|
||||||
|
import sonia.scm.EagerSingletonModule;
|
||||||
|
import sonia.scm.ServletContextListenerHolder;
|
||||||
|
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.servlet.ServletContextEvent;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
class InjectionLifeCycle {
|
||||||
|
|
||||||
|
private final Injector injector;
|
||||||
|
|
||||||
|
InjectionLifeCycle(Injector injector) {
|
||||||
|
this.injector = injector;
|
||||||
|
}
|
||||||
|
|
||||||
|
void initialize() {
|
||||||
|
initializeEagerSingletons();
|
||||||
|
initializeServletContextListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void shutdown() {
|
||||||
|
destroyServletContextListeners();
|
||||||
|
closeRegisteredCloseables();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeServletContextListeners() {
|
||||||
|
ServletContextListenerHolder instance = injector.getInstance(ServletContextListenerHolder.class);
|
||||||
|
ServletContext context = injector.getInstance(Key.get(ServletContext.class, Default.class));
|
||||||
|
instance.contextInitialized(new ServletContextEvent(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeEagerSingletons() {
|
||||||
|
findInstance(EagerSingletonModule.class).ifPresent(m -> m.initialize(injector));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeRegisteredCloseables() {
|
||||||
|
findInstance(CloseableModule.class).ifPresent(CloseableModule::closeAll);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void destroyServletContextListeners() {
|
||||||
|
ServletContextListenerHolder instance = injector.getInstance(ServletContextListenerHolder.class);
|
||||||
|
ServletContext context = injector.getInstance(Key.get(ServletContext.class, Default.class));
|
||||||
|
instance.contextDestroyed(new ServletContextEvent(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> Optional<T> findInstance(Class<T> clazz) {
|
||||||
|
Binding<T> binding = injector.getExistingBinding(Key.get(clazz));
|
||||||
|
if (binding != null) {
|
||||||
|
return Optional.of(binding.getProvider().get());
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
11
scm-webapp/src/main/java/sonia/scm/boot/ModuleProvider.java
Normal file
11
scm-webapp/src/main/java/sonia/scm/boot/ModuleProvider.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package sonia.scm.boot;
|
||||||
|
|
||||||
|
import com.google.inject.Module;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public interface ModuleProvider {
|
||||||
|
|
||||||
|
Collection<Module> createModules();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package sonia.scm.boot;
|
||||||
|
|
||||||
|
import com.google.inject.servlet.ServletModule;
|
||||||
|
import sonia.scm.Default;
|
||||||
|
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
|
||||||
|
class ServletContextModule extends ServletModule {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configureServlets() {
|
||||||
|
bind(ServletContext.class).annotatedWith(Default.class).toInstance(getServletContext());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
package sonia.scm.boot;
|
package sonia.scm.boot;
|
||||||
|
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.inject.Guice;
|
import com.google.inject.Module;
|
||||||
import com.google.inject.Injector;
|
|
||||||
import com.google.inject.multibindings.Multibinder;
|
import com.google.inject.multibindings.Multibinder;
|
||||||
import com.google.inject.servlet.GuiceServletContextListener;
|
|
||||||
import com.google.inject.servlet.ServletModule;
|
import com.google.inject.servlet.ServletModule;
|
||||||
import sonia.scm.Default;
|
import sonia.scm.Default;
|
||||||
import sonia.scm.SCMContext;
|
import sonia.scm.SCMContext;
|
||||||
@@ -14,17 +13,16 @@ import sonia.scm.template.MustacheTemplateEngine;
|
|||||||
import sonia.scm.template.TemplateEngine;
|
import sonia.scm.template.TemplateEngine;
|
||||||
import sonia.scm.template.TemplateEngineFactory;
|
import sonia.scm.template.TemplateEngineFactory;
|
||||||
|
|
||||||
import javax.servlet.ServletContext;
|
|
||||||
import javax.servlet.ServletContextListener;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
final class SingleView {
|
final class SingleView {
|
||||||
|
|
||||||
private SingleView() {
|
private SingleView() {
|
||||||
}
|
}
|
||||||
|
|
||||||
static ServletContextListener error(Throwable throwable) {
|
static SingleViewModuleProvider error(Throwable throwable) {
|
||||||
String error = Throwables.getStackTraceAsString(throwable);
|
String error = Throwables.getStackTraceAsString(throwable);
|
||||||
|
|
||||||
ViewController controller = new SimpleViewController("/templates/error.mustache", request -> {
|
ViewController controller = new SimpleViewController("/templates/error.mustache", request -> {
|
||||||
@@ -34,30 +32,30 @@ final class SingleView {
|
|||||||
);
|
);
|
||||||
return new View(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, model);
|
return new View(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, model);
|
||||||
});
|
});
|
||||||
return new SingleViewContextListener(controller);
|
return new SingleViewModuleProvider(controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ServletContextListener view(String template, int sc) {
|
static SingleViewModuleProvider view(String template, int sc) {
|
||||||
ViewController controller = new SimpleViewController(template, request -> {
|
ViewController controller = new SimpleViewController(template, request -> {
|
||||||
Object model = ImmutableMap.of(
|
Object model = ImmutableMap.of(
|
||||||
"contextPath", request.getContextPath()
|
"contextPath", request.getContextPath()
|
||||||
);
|
);
|
||||||
return new View(sc, model);
|
return new View(sc, model);
|
||||||
});
|
});
|
||||||
return new SingleViewContextListener(controller);
|
return new SingleViewModuleProvider(controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class SingleViewContextListener extends GuiceServletContextListener {
|
private static class SingleViewModuleProvider implements ModuleProvider {
|
||||||
|
|
||||||
private final ViewController controller;
|
private final ViewController controller;
|
||||||
|
|
||||||
private SingleViewContextListener(ViewController controller) {
|
private SingleViewModuleProvider(ViewController controller) {
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Injector getInjector() {
|
public Collection<Module> createModules() {
|
||||||
return Guice.createInjector(new SingleViewModule(controller));
|
return ImmutableList.of(new ServletContextModule(), new SingleViewModule(controller));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,8 +82,6 @@ final class SingleView {
|
|||||||
MustacheTemplateEngine.class);
|
MustacheTemplateEngine.class);
|
||||||
bind(TemplateEngineFactory.class);
|
bind(TemplateEngineFactory.class);
|
||||||
|
|
||||||
bind(ServletContext.class).annotatedWith(Default.class).toInstance(getServletContext());
|
|
||||||
|
|
||||||
serve("/images/*", "/styles/*", "/favicon.ico").with(StaticResourceServlet.class);
|
serve("/images/*", "/styles/*", "/favicon.ico").with(StaticResourceServlet.class);
|
||||||
serve("/*").with(SingleViewServlet.class);
|
serve("/*").with(SingleViewServlet.class);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
package sonia.scm.update;
|
package sonia.scm.update;
|
||||||
|
|
||||||
import com.google.inject.Injector;
|
import com.google.inject.Injector;
|
||||||
import com.google.inject.servlet.GuiceServletContextListener;
|
import com.google.inject.Module;
|
||||||
|
import sonia.scm.boot.ModuleProvider;
|
||||||
import sonia.scm.update.repository.XmlRepositoryV1UpdateStep;
|
import sonia.scm.update.repository.XmlRepositoryV1UpdateStep;
|
||||||
|
|
||||||
public class MigrationWizardContextListener extends GuiceServletContextListener {
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
public class MigrationWizardModuleProvider implements ModuleProvider {
|
||||||
|
|
||||||
private final Injector bootstrapInjector;
|
private final Injector bootstrapInjector;
|
||||||
|
|
||||||
public MigrationWizardContextListener(Injector bootstrapInjector) {
|
public MigrationWizardModuleProvider(Injector bootstrapInjector) {
|
||||||
this.bootstrapInjector = bootstrapInjector;
|
this.bootstrapInjector = bootstrapInjector;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,7 +21,7 @@ public class MigrationWizardContextListener extends GuiceServletContextListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Injector getInjector() {
|
public Collection<Module> createModules() {
|
||||||
return bootstrapInjector.createChildInjector(new MigrationWizardModule());
|
return Collections.singleton(new MigrationWizardModule());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
package sonia.scm.boot;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.inject.AbstractModule;
|
||||||
|
import com.google.inject.Guice;
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
import com.google.inject.Module;
|
||||||
|
import com.google.inject.multibindings.Multibinder;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import sonia.scm.CloseableModule;
|
||||||
|
import sonia.scm.Default;
|
||||||
|
import sonia.scm.EagerSingleton;
|
||||||
|
import sonia.scm.EagerSingletonModule;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.servlet.ServletContextEvent;
|
||||||
|
import javax.servlet.ServletContextListener;
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class InjectionLifeCycleTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ServletContext servletContext;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldInitializeEagerSingletons() {
|
||||||
|
Injector injector = initialize(new EagerSingletonModule(), new EagerModule());
|
||||||
|
|
||||||
|
Messenger messenger = injector.getInstance(Messenger.class);
|
||||||
|
assertThat(messenger.receive()).isEqualTo("eager baby!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotThrowAnExceptionWithoutEagerSingletons() {
|
||||||
|
Injector injector = initialize(new EagerSingletonModule());
|
||||||
|
|
||||||
|
Messenger messenger = injector.getInstance(Messenger.class);
|
||||||
|
assertThat(messenger.receive()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldInitializeServletContextListeners() {
|
||||||
|
Injector injector = initialize(new ServletContextListenerModule());
|
||||||
|
|
||||||
|
Messenger messenger = injector.getInstance(Messenger.class);
|
||||||
|
assertThat(messenger.receive()).isEqualTo("+4+2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCallDestroyOnServletContextListeners() {
|
||||||
|
Injector injector = createInjector(servletContext, new ServletContextListenerModule());
|
||||||
|
|
||||||
|
InjectionLifeCycle lifeCycle = new InjectionLifeCycle(injector);
|
||||||
|
lifeCycle.shutdown();
|
||||||
|
|
||||||
|
Messenger messenger = injector.getInstance(Messenger.class);
|
||||||
|
assertThat(messenger.receive()).isEqualTo("-4-2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCloseInstantiatedCloseables() {
|
||||||
|
Injector injector = createInjector(servletContext, new FortyTwoModule(), new CloseableModule());
|
||||||
|
|
||||||
|
injector.getInstance(Two.class);
|
||||||
|
injector.getInstance(Four.class);
|
||||||
|
|
||||||
|
|
||||||
|
InjectionLifeCycle lifeCycle = new InjectionLifeCycle(injector);
|
||||||
|
lifeCycle.shutdown();
|
||||||
|
|
||||||
|
Messenger messenger = injector.getInstance(Messenger.class);
|
||||||
|
assertThat(messenger.receive()).isEqualTo("42");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Injector initialize(Module... modules) {
|
||||||
|
Injector injector = createInjector(servletContext, modules);
|
||||||
|
|
||||||
|
InjectionLifeCycle lifeCycle = new InjectionLifeCycle(injector);
|
||||||
|
lifeCycle.initialize();
|
||||||
|
|
||||||
|
return injector;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EagerModule extends AbstractModule {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
bind(ImEager.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EagerSingleton
|
||||||
|
public static class ImEager {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ImEager(Messenger messenger) {
|
||||||
|
messenger.send("eager baby!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class FortyTwoModule extends AbstractModule {
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
bind(Four.class);
|
||||||
|
bind(Two.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public static class Four implements Closeable {
|
||||||
|
|
||||||
|
private final Messenger messenger;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public Four(Messenger messenger) {
|
||||||
|
this.messenger = messenger;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
messenger.append("4");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public static class Two implements Closeable {
|
||||||
|
|
||||||
|
private final Messenger messenger;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public Two(Messenger messenger) {
|
||||||
|
this.messenger = messenger;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
messenger.append("2");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Injector createInjector(ServletContext context, Module... modules) {
|
||||||
|
List<Module> moduleList = new ArrayList<>();
|
||||||
|
moduleList.add(new ServletContextModule(context));
|
||||||
|
moduleList.addAll(Arrays.asList(modules));
|
||||||
|
|
||||||
|
return Guice.createInjector(moduleList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ServletContextModule extends AbstractModule {
|
||||||
|
|
||||||
|
private final ServletContext servletContext;
|
||||||
|
|
||||||
|
ServletContextModule(ServletContext servletContext) {
|
||||||
|
this.servletContext = servletContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
bind(ServletContext.class).annotatedWith(Default.class).toInstance(servletContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ServletContextListenerModule extends AbstractModule {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
Multibinder<ServletContextListener> multibinder = Multibinder.newSetBinder(binder(), ServletContextListener.class);
|
||||||
|
multibinder.addBinding().to(AppendingFourServletContextListener.class);
|
||||||
|
multibinder.addBinding().to(AppendingTwoServletContextListener.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AppendingFourServletContextListener extends AppendingServletContextListener {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public AppendingFourServletContextListener(Messenger messenger) {
|
||||||
|
super(messenger);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getSign() {
|
||||||
|
return "4";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AppendingTwoServletContextListener extends AppendingServletContextListener {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public AppendingTwoServletContextListener(Messenger messenger) {
|
||||||
|
super(messenger);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getSign() {
|
||||||
|
return "2";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static abstract class AppendingServletContextListener implements ServletContextListener {
|
||||||
|
|
||||||
|
private final Messenger messenger;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public AppendingServletContextListener(Messenger messenger) {
|
||||||
|
this.messenger = messenger;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void contextInitialized(ServletContextEvent sce) {
|
||||||
|
send("+");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void contextDestroyed(ServletContextEvent sce) {
|
||||||
|
send("-");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void send(String prefix) {
|
||||||
|
messenger.append(prefix + getSign());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract String getSign();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public static class Messenger {
|
||||||
|
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
void send(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
void append(String messageToAppend) {
|
||||||
|
send(Strings.nullToEmpty(message) + messageToAppend);
|
||||||
|
}
|
||||||
|
|
||||||
|
String receive() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package sonia.scm.boot;
|
||||||
|
|
||||||
|
import com.google.inject.Guice;
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
import com.google.inject.servlet.GuiceFilter;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import sonia.scm.Default;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.servlet.FilterConfig;
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class ServletContextModuleTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ServletContext servletContext;
|
||||||
|
|
||||||
|
private GuiceFilter guiceFilter;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUpEnvironment() throws ServletException {
|
||||||
|
guiceFilter = new GuiceFilter();
|
||||||
|
FilterConfig filterConfig = mock(FilterConfig.class);
|
||||||
|
when(filterConfig.getServletContext()).thenReturn(servletContext);
|
||||||
|
|
||||||
|
guiceFilter.init(filterConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void tearDownEnvironment() {
|
||||||
|
guiceFilter.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldBeAbleToInjectServletContext() {
|
||||||
|
Injector injector = Guice.createInjector(new ServletContextModule());
|
||||||
|
WebComponent instance = injector.getInstance(WebComponent.class);
|
||||||
|
assertThat(instance.context).isSameAs(servletContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class WebComponent {
|
||||||
|
|
||||||
|
private ServletContext context;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public WebComponent(@Default ServletContext context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package sonia.scm.boot;
|
package sonia.scm.boot;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
|
|||||||
@@ -1,20 +1,17 @@
|
|||||||
package sonia.scm.boot;
|
package sonia.scm.boot;
|
||||||
|
|
||||||
|
import com.google.inject.Guice;
|
||||||
import com.google.inject.Injector;
|
import com.google.inject.Injector;
|
||||||
import com.google.inject.servlet.GuiceFilter;
|
import com.google.inject.servlet.GuiceFilter;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.ArgumentCaptor;
|
|
||||||
import org.mockito.Captor;
|
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
import javax.servlet.FilterConfig;
|
import javax.servlet.FilterConfig;
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
import javax.servlet.ServletContextEvent;
|
|
||||||
import javax.servlet.ServletContextListener;
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -26,22 +23,19 @@ import static org.mockito.Mockito.*;
|
|||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
class SingleViewTest {
|
class SingleViewTest {
|
||||||
|
|
||||||
@Mock
|
|
||||||
private ServletContext servletContext;
|
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private HttpServletRequest request;
|
private HttpServletRequest request;
|
||||||
|
|
||||||
@Captor
|
|
||||||
private ArgumentCaptor<Injector> captor;
|
|
||||||
|
|
||||||
private GuiceFilter guiceFilter;
|
private GuiceFilter guiceFilter;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUpGuiceFilter() throws ServletException {
|
void setUpGuiceFilter() throws ServletException {
|
||||||
guiceFilter = new GuiceFilter();
|
guiceFilter = new GuiceFilter();
|
||||||
|
|
||||||
|
ServletContext servletContext = mock(ServletContext.class);
|
||||||
FilterConfig config = mock(FilterConfig.class);
|
FilterConfig config = mock(FilterConfig.class);
|
||||||
doReturn(servletContext).when(config).getServletContext();
|
doReturn(servletContext).when(config).getServletContext();
|
||||||
|
|
||||||
guiceFilter.init(config);
|
guiceFilter.init(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,10 +46,10 @@ class SingleViewTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldCreateViewControllerForView() {
|
void shouldCreateViewControllerForView() {
|
||||||
ServletContextListener listener = SingleView.view("/my-template", 409);
|
ModuleProvider moduleProvider = SingleView.view("/my-template", 409);
|
||||||
when(request.getContextPath()).thenReturn("/scm");
|
when(request.getContextPath()).thenReturn("/scm");
|
||||||
|
|
||||||
ViewController instance = findViewController(listener);
|
ViewController instance = findViewController(moduleProvider);
|
||||||
assertThat(instance.getTemplate()).isEqualTo("/my-template");
|
assertThat(instance.getTemplate()).isEqualTo("/my-template");
|
||||||
|
|
||||||
View view = instance.createView(request);
|
View view = instance.createView(request);
|
||||||
@@ -64,17 +58,17 @@ class SingleViewTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldCreateViewControllerForError() {
|
void shouldCreateViewControllerForError() {
|
||||||
ServletContextListener listener = SingleView.error(new IOException("awesome io"));
|
ModuleProvider moduleProvider = SingleView.error(new IOException("awesome io"));
|
||||||
when(request.getContextPath()).thenReturn("/scm");
|
when(request.getContextPath()).thenReturn("/scm");
|
||||||
|
|
||||||
ViewController instance = findViewController(listener);
|
ViewController instance = findViewController(moduleProvider);
|
||||||
assertErrorViewController(instance, "awesome io");
|
assertErrorViewController(instance, "awesome io");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldBindServlets() {
|
void shouldBindServlets() {
|
||||||
ServletContextListener listener = SingleView.error(new IOException("awesome io"));
|
ModuleProvider moduleProvider = SingleView.error(new IOException("awesome io"));
|
||||||
Injector injector = findInjector(listener);
|
Injector injector = Guice.createInjector(moduleProvider.createModules());
|
||||||
|
|
||||||
assertThat(injector.getInstance(StaticResourceServlet.class)).isNotNull();
|
assertThat(injector.getInstance(StaticResourceServlet.class)).isNotNull();
|
||||||
assertThat(injector.getInstance(SingleViewServlet.class)).isNotNull();
|
assertThat(injector.getInstance(SingleViewServlet.class)).isNotNull();
|
||||||
@@ -94,18 +88,9 @@ class SingleViewTest {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ViewController findViewController(ServletContextListener listener) {
|
private ViewController findViewController(ModuleProvider moduleProvider) {
|
||||||
Injector injector = findInjector(listener);
|
Injector injector = Guice.createInjector(moduleProvider.createModules());
|
||||||
return injector.getInstance(ViewController.class);
|
return injector.getInstance(ViewController.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Injector findInjector(ServletContextListener listener) {
|
|
||||||
listener.contextInitialized(new ServletContextEvent(servletContext));
|
|
||||||
|
|
||||||
verify(servletContext).setAttribute(anyString(), captor.capture());
|
|
||||||
|
|
||||||
return captor.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user