diff --git a/scm-core/src/main/java/sonia/scm/BasicContextProvider.java b/scm-core/src/main/java/sonia/scm/BasicContextProvider.java index 6954c03832..f4ed04c3f5 100644 --- a/scm-core/src/main/java/sonia/scm/BasicContextProvider.java +++ b/scm-core/src/main/java/sonia/scm/BasicContextProvider.java @@ -126,19 +126,6 @@ public class BasicContextProvider implements SCMContextProvider return baseDirectory.toPath().resolve(path); } - - /** - * {@inheritDoc} - */ - @Override - public void close() throws IOException {} - - /** - * {@inheritDoc} - */ - @Override - public void init() {} - //~--- get methods ---------------------------------------------------------- /** diff --git a/scm-core/src/main/java/sonia/scm/SCMContext.java b/scm-core/src/main/java/sonia/scm/SCMContext.java index f2c1af81d0..5af5d9f7d9 100644 --- a/scm-core/src/main/java/sonia/scm/SCMContext.java +++ b/scm-core/src/main/java/sonia/scm/SCMContext.java @@ -94,8 +94,6 @@ public final class SCMContext { provider = new BasicContextProvider(); } - - provider.init(); } } } diff --git a/scm-core/src/main/java/sonia/scm/SCMContextProvider.java b/scm-core/src/main/java/sonia/scm/SCMContextProvider.java index 93918770c8..dac03baed0 100644 --- a/scm-core/src/main/java/sonia/scm/SCMContextProvider.java +++ b/scm-core/src/main/java/sonia/scm/SCMContextProvider.java @@ -46,25 +46,14 @@ import java.nio.file.Path; * * @author Sebastian Sdorra */ -public interface SCMContextProvider extends Closeable -{ - - /** - * Initializes the {@link SCMContextProvider}. - * This method is called when the SCM manager is started. - * - */ - public void init(); - - //~--- get methods ---------------------------------------------------------- - +public interface SCMContextProvider { /** * Returns the base directory of the SCM-Manager. * * * @return base directory of the SCM-Manager */ - public File getBaseDirectory(); + File getBaseDirectory(); /** * Resolves the given path against the base directory. @@ -84,7 +73,7 @@ public interface SCMContextProvider extends Closeable * @return stage of SCM-Manager * @since 1.12 */ - public Stage getStage(); + Stage getStage(); /** * Returns a exception which is occurred on context startup. @@ -94,7 +83,7 @@ public interface SCMContextProvider extends Closeable * @return startup exception of null * @since 1.14 */ - public Throwable getStartupError(); + Throwable getStartupError(); /** * Returns the version of the SCM-Manager. @@ -102,5 +91,5 @@ public interface SCMContextProvider extends Closeable * * @return version of the SCM-Manager */ - public String getVersion(); + String getVersion(); } diff --git a/scm-core/src/main/java/sonia/scm/boot/RestartEvent.java b/scm-core/src/main/java/sonia/scm/lifecycle/RestartEvent.java similarity index 99% rename from scm-core/src/main/java/sonia/scm/boot/RestartEvent.java rename to scm-core/src/main/java/sonia/scm/lifecycle/RestartEvent.java index 9aab8d18ae..1978cb9d7c 100644 --- a/scm-core/src/main/java/sonia/scm/boot/RestartEvent.java +++ b/scm-core/src/main/java/sonia/scm/lifecycle/RestartEvent.java @@ -29,7 +29,7 @@ -package sonia.scm.boot; +package sonia.scm.lifecycle; //~--- non-JDK imports -------------------------------------------------------- diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/TempSCMContextProvider.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/TempSCMContextProvider.java index 0a0064ad44..32cf7b171c 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/TempSCMContextProvider.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/TempSCMContextProvider.java @@ -50,32 +50,6 @@ import java.nio.file.Path; public class TempSCMContextProvider implements SCMContextProvider { - /** - * Method description - * - * - * @throws IOException - */ - @Override - public void close() throws IOException - { - - // do nothing - } - - /** - * Method description - * - */ - @Override - public void init() - { - - // do nothing - } - - //~--- get methods ---------------------------------------------------------- - /** * Method description * diff --git a/scm-webapp/src/main/java/sonia/scm/HttpSessionListenerHolder.java b/scm-webapp/src/main/java/sonia/scm/HttpSessionListenerHolder.java deleted file mode 100644 index f7f5003989..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/HttpSessionListenerHolder.java +++ /dev/null @@ -1,171 +0,0 @@ - -package sonia.scm; - -//~--- non-JDK imports -------------------------------------------------------- - -import com.google.common.collect.Sets; -import com.google.inject.Injector; -import com.google.inject.Key; -import com.google.inject.TypeLiteral; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import sonia.scm.plugin.Extension; - -//~--- JDK imports ------------------------------------------------------------ - -import java.util.Set; - -import javax.servlet.ServletContext; -import javax.servlet.http.HttpSession; -import javax.servlet.http.HttpSessionEvent; -import javax.servlet.http.HttpSessionListener; - -/** - * Dispatcher for {@link HttpSessionEvent}. The {@link HttpSessionListenerHolder} - * loads all registered {@link HttpSessionListener}s from the {@link Injector} - * and delegates the events to the them. {@link HttpSessionListener} can be - * registered with the {@link Extension} annotation. - * - * @author Sebastian Sdorra - * @since 1.42 - */ -public class HttpSessionListenerHolder implements HttpSessionListener -{ - - /** key type of the session listeners */ - private static final Key> KEY = - Key.get(new TypeLiteral>() {} - ); - - /** logger for HttpSessionListenerHolder */ - private static final Logger logger = - LoggerFactory.getLogger(HttpSessionListenerHolder.class); - - //~--- constructors --------------------------------------------------------- - - /** - * Constructs a new HttpSessionListenerHolder. - * - */ - public HttpSessionListenerHolder() - { - if (logger.isDebugEnabled()) - { - logger.debug("create instance of {}", - HttpSessionListenerHolder.class.getName()); - } - } - - //~--- methods -------------------------------------------------------------- - - /** - * Delegates the create session event to all registered - * {@ĺink HttpSessionListener}s. - * - * - * @param event session event - */ - @Override - public void sessionCreated(HttpSessionEvent event) - { - if (listenerSet == null) - { - listenerSet = loadListeners(event); - } - - dispatch(event, true); - } - - /** - * Delegates the destroy session event to all registered - * {@ĺink HttpSessionListener}s. - * - * - * @param event session event - */ - @Override - public void sessionDestroyed(HttpSessionEvent event) - { - dispatch(event, false); - } - - /** - * Dispatch session events. - * - * - * @param event session event - * @param create {@code true} if the event is a create event - */ - private void dispatch(HttpSessionEvent event, boolean create) - { - if (listenerSet != null) - { - for (HttpSessionListener listener : listenerSet) - { - if (create) - { - listener.sessionCreated(event); - } - else - { - listener.sessionDestroyed(event); - } - } - } - else - { - logger.warn( - "could not dispatch session event, because holder is not initialized"); - } - } - - /** - * Load listeners from {@link Injector} which is stored in the - * {@link ServletContext}. - * - * - * @param event session event - * - * @return set of session listeners - */ - private synchronized Set loadListeners( - HttpSessionEvent event) - { - Set listeners = null; - HttpSession session = event.getSession(); - - if (session != null) - { - Injector injector = (Injector) session.getServletContext().getAttribute( - Injector.class.getName()); - - if (injector != null) - { - logger.debug("load HttpSessionListeners from injector"); - listeners = injector.getInstance(KEY); - } - else - { - logger.error("could not find injector in servletContext"); - } - - if (listeners == null) - { - listeners = Sets.newHashSet(); - } - } - else - { - logger.warn("received session event without session"); - } - - return listeners; - } - - //~--- fields --------------------------------------------------------------- - - /** listener set */ - private Set listenerSet; -} diff --git a/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java b/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java deleted file mode 100644 index cb57e88933..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java +++ /dev/null @@ -1,196 +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 plugins; - private Injector injector; - - public interface Factory { - ScmContextListener create(ClassLoader parent, Set plugins); - } - - @Inject - public ScmContextListener(@Assisted ClassLoader parent, @Assisted Set plugins) - { - this.parent = parent; - this.plugins = plugins; - } - - public Set 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 getModules(ServletContext context) { - DefaultPluginLoader pluginLoader = new DefaultPluginLoader(context, parent, plugins); - - ClassOverrides overrides = ClassOverrides.findOverrides(pluginLoader.getUberClassLoader()); - List 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 moduleList) { - for (Class 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() { - // close Scheduler - IOUtil.close(injector.getInstance(Scheduler.class)); - - // close RepositoryManager - IOUtil.close(injector.getInstance(RepositoryManager.class)); - - // close GroupManager - IOUtil.close(injector.getInstance(GroupManager.class)); - - // close UserManager - IOUtil.close(injector.getInstance(UserManager.class)); - - // close CacheManager - IOUtil.close(injector.getInstance(CacheManager.class)); - } - - private void destroyServletContextListeners(ServletContextEvent event) { - injector.getInstance(ServletContextListenerHolder.class).contextDestroyed(event); - } -} diff --git a/scm-webapp/src/main/java/sonia/scm/ScmInitializerModule.java b/scm-webapp/src/main/java/sonia/scm/ScmInitializerModule.java deleted file mode 100644 index 16675749ac..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/ScmInitializerModule.java +++ /dev/null @@ -1,149 +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; - -//~--- non-JDK imports -------------------------------------------------------- - -import com.google.inject.AbstractModule; -import com.google.inject.TypeLiteral; -import com.google.inject.matcher.AbstractMatcher; -import com.google.inject.matcher.Matcher; -import com.google.inject.spi.InjectionListener; -import com.google.inject.spi.TypeEncounter; -import com.google.inject.spi.TypeListener; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author Sebastian Sdorra - */ -public class ScmInitializerModule extends AbstractModule -{ - - /** - * the logger for ScmInitializerModule - */ - private static final Logger logger = - LoggerFactory.getLogger(ScmInitializerModule.class); - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - */ - @Override - protected void configure() - { - bindListener(isSubtypeOf(Initable.class), new TypeListener() - { - - @Override - public void hear(TypeLiteral type, TypeEncounter encounter) - { - encounter.register(new InjectionListener() - { - @Override - public void afterInjection(Object i) - { - if (logger.isTraceEnabled()) - { - logger.trace("initialize initable {}", i.getClass()); - } - - Initable initable = (Initable) i; - - initable.init(SCMContext.getContext()); - } - }); - } - }); - } - - /** - * Method description - * - * - * @param subtype - * @param supertype - * - * @return - */ - private boolean typeIsSubtypeOf(TypeLiteral subtype, - TypeLiteral supertype) - { - - // First check that raw types are compatible - // Then check that generic types are compatible! HOW???? - return (subtype.equals(supertype) - || (supertype.getRawType().isAssignableFrom(subtype.getRawType()) - && supertype.equals(subtype.getSupertype(supertype.getRawType())))); - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param supertype - * - * @return - */ - private Matcher> isSubtypeOf(final Class supertype) - { - return isSubtypeOf(TypeLiteral.get(supertype)); - } - - /** - * Method description - * - * - * @param supertype - * - * @return - */ - private Matcher> isSubtypeOf(final TypeLiteral supertype) - { - return new AbstractMatcher>() - { - @Override - public boolean matches(TypeLiteral type) - { - return typeIsSubtypeOf(type, supertype); - } - }; - } -} diff --git a/scm-webapp/src/main/java/sonia/scm/boot/StaticResourceServlet.java b/scm-webapp/src/main/java/sonia/scm/StaticResourceServlet.java similarity index 92% rename from scm-webapp/src/main/java/sonia/scm/boot/StaticResourceServlet.java rename to scm-webapp/src/main/java/sonia/scm/StaticResourceServlet.java index cac801ec60..57ea0dde09 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/StaticResourceServlet.java +++ b/scm-webapp/src/main/java/sonia/scm/StaticResourceServlet.java @@ -1,4 +1,4 @@ -package sonia.scm.boot; +package sonia.scm; import com.github.sdorra.webresources.CacheControl; import com.github.sdorra.webresources.WebResourceSender; @@ -14,6 +14,9 @@ import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; +/** + * Serves static resources from servlet context. + */ @Singleton public class StaticResourceServlet extends HttpServlet { @@ -35,7 +38,7 @@ public class StaticResourceServlet extends HttpServlet { response.setStatus(HttpServletResponse.SC_NOT_FOUND); } } catch (IOException ex) { - LOG.warn("failed to servce resource", ex); + LOG.warn("failed to serve resource", ex); response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } diff --git a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextListener.java b/scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextListener.java deleted file mode 100644 index 572ff99d49..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextListener.java +++ /dev/null @@ -1,440 +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.boot; - -import com.google.common.base.Charsets; -import com.google.common.collect.ImmutableList; -import com.google.common.io.Files; -import com.google.inject.AbstractModule; -import com.google.inject.Guice; -import com.google.inject.Injector; -import com.google.inject.Module; -import com.google.inject.assistedinject.FactoryModuleBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import sonia.scm.EagerSingletonModule; -import sonia.scm.SCMContext; -import sonia.scm.ScmContextListener; -import sonia.scm.ScmEventBusModule; -import sonia.scm.ScmInitializerModule; -import sonia.scm.migration.UpdateException; -import sonia.scm.plugin.DefaultPluginLoader; -import sonia.scm.plugin.Plugin; -import sonia.scm.plugin.PluginException; -import sonia.scm.plugin.PluginLoadException; -import sonia.scm.plugin.PluginLoader; -import sonia.scm.plugin.PluginWrapper; -import sonia.scm.plugin.PluginsInternal; -import sonia.scm.plugin.SmpArchive; -import sonia.scm.update.MigrationWizardContextListener; -import sonia.scm.update.UpdateEngine; -import sonia.scm.util.IOUtil; - -import javax.servlet.ServletContext; -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; -import javax.servlet.http.HttpServletResponse; -import javax.xml.bind.DataBindingException; -import javax.xml.bind.JAXB; -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -/** - * - * @author Sebastian Sdorra - */ -public class BootstrapContextListener implements ServletContextListener { - - /** Field description */ - private static final String DIRECTORY_PLUGINS = "plugins"; - - /** Field description */ - private static final String PLUGIN_DIRECTORY = "/WEB-INF/plugins/"; - - /** - * the logger for BootstrapContextListener - */ - private static final Logger logger = - LoggerFactory.getLogger(BootstrapContextListener.class); - - /** Field description */ - private static final String PLUGIN_COREINDEX = - PLUGIN_DIRECTORY.concat("plugin-index.xml"); - - //~--- methods -------------------------------------------------------------- - - private final ClassLoaderLifeCycle classLoaderLifeCycle = ClassLoaderLifeCycle.create(); - - /** - * Method description - * - * - * @param sce - */ - @Override - public void contextDestroyed(ServletContextEvent sce) { - contextListener.contextDestroyed(sce); - classLoaderLifeCycle.shutdown(); - - context = null; - contextListener = null; - } - - /** - * Method description - * - * - * @param sce - */ - @Override - public void contextInitialized(ServletContextEvent sce) { - classLoaderLifeCycle.init(); - - context = sce.getServletContext(); - - createContextListener(); - - contextListener.contextInitialized(sce); - } - - private void createContextListener() { - 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() { - Set plugins; - PluginLoader pluginLoader; - - try { - File pluginDirectory = getPluginDirectory(); - - renameOldPluginsFolder(pluginDirectory); - - if (!isCorePluginExtractionDisabled()) { - extractCorePlugins(context, pluginDirectory); - } else { - logger.info("core plugin extraction is disabled"); - } - - - plugins = PluginsInternal.collectPlugins(classLoaderLifeCycle, pluginDirectory.toPath()); - - pluginLoader = new DefaultPluginLoader(context, classLoaderLifeCycle.getBootstrapClassLoader(), plugins); - - } catch (IOException ex) { - throw new PluginLoadException("could not load plugins", ex); - } - - Injector bootstrapInjector = createBootstrapInjector(pluginLoader); - - startEitherMigrationOrNormalServlet(classLoaderLifeCycle.getBootstrapClassLoader(), plugins, pluginLoader, bootstrapInjector); - } - - private void startEitherMigrationOrNormalServlet(ClassLoader cl, Set plugins, PluginLoader pluginLoader, Injector bootstrapInjector) { - MigrationWizardContextListener wizardContextListener = prepareWizardIfNeeded(bootstrapInjector); - - if (wizardContextListener.wizardNecessary()) { - contextListener = wizardContextListener; - } else { - processUpdates(pluginLoader, bootstrapInjector); - contextListener = bootstrapInjector.getInstance(ScmContextListener.Factory.class).create(cl, plugins); - } - } - - private void renameOldPluginsFolder(File pluginDirectory) { - if (new File(pluginDirectory, "classpath.xml").exists()) { - File backupDirectory = new File(pluginDirectory.getParentFile(), "plugins.v1"); - boolean renamed = pluginDirectory.renameTo(backupDirectory); - if (renamed) { - logger.warn("moved old plugins directory to {}", backupDirectory); - } else { - throw new UpdateException("could not rename existing v1 plugin directory"); - } - } - } - - private MigrationWizardContextListener prepareWizardIfNeeded(Injector bootstrapInjector) { - return new MigrationWizardContextListener(bootstrapInjector); - } - - private Injector createBootstrapInjector(PluginLoader pluginLoader) { - Module scmContextListenerModule = new ScmContextListenerModule(); - BootstrapModule bootstrapModule = new BootstrapModule(pluginLoader); - ScmInitializerModule scmInitializerModule = new ScmInitializerModule(); - EagerSingletonModule eagerSingletonModule = new EagerSingletonModule(); - ScmEventBusModule scmEventBusModule = new ScmEventBusModule(); - - return Guice.createInjector( - bootstrapModule, - scmContextListenerModule, - scmEventBusModule, - scmInitializerModule, - eagerSingletonModule - ); - } - - private void processUpdates(PluginLoader pluginLoader, Injector bootstrapInjector) { - Injector updateInjector = bootstrapInjector.createChildInjector(new UpdateStepModule(pluginLoader)); - - UpdateEngine updateEngine = updateInjector.getInstance(UpdateEngine.class); - updateEngine.update(); - } - - private boolean isCorePluginExtractionDisabled() { - return Boolean.getBoolean("sonia.scm.boot.disable-core-plugin-extraction"); - } - - /** - * Method description - * - * - * @param context - * @param pluginDirectory - * @param entry - * - * @throws IOException - */ - private void extractCorePlugin(ServletContext context, File pluginDirectory, - PluginIndexEntry entry) - throws IOException { - URL url = context.getResource(PLUGIN_DIRECTORY.concat(entry.getName())); - SmpArchive archive = SmpArchive.create(url); - Plugin plugin = archive.getPlugin(); - - File directory = PluginsInternal.createPluginDirectory(pluginDirectory, - plugin); - File checksumFile = PluginsInternal.getChecksumFile(directory); - - if (!directory.exists()) { - logger.warn("install plugin {}", plugin.getInformation().getId()); - PluginsInternal.extract(archive, entry.getChecksum(), directory, - checksumFile, true); - } else if (!checksumFile.exists()) { - logger.warn("plugin directory {} exists without checksum file.", - directory); - PluginsInternal.extract(archive, entry.getChecksum(), directory, - checksumFile, true); - } else { - String checksum = Files.toString(checksumFile, Charsets.UTF_8).trim(); - - if (checksum.equals(entry.getChecksum())) { - logger.debug("plugin {} is up to date", - plugin.getInformation().getId()); - } else { - logger.warn("checksum mismatch of pluing {}, start update", - plugin.getInformation().getId()); - PluginsInternal.extract(archive, entry.getChecksum(), directory, - checksumFile, true); - } - } - } - - /** - * Method description - * - * - * @param context - * @param pluginDirectory - * - * @throws IOException - */ - private void extractCorePlugins(ServletContext context, File pluginDirectory) throws IOException { - IOUtil.mkdirs(pluginDirectory); - - PluginIndex index = readCorePluginIndex(context); - - for (PluginIndexEntry entry : index) { - extractCorePlugin(context, pluginDirectory, entry); - } - } - - /** - * Method description - * - * - * @param context - * - * @return - */ - private PluginIndex readCorePluginIndex(ServletContext context) { - PluginIndex index = null; - - try { - URL indexUrl = context.getResource(PLUGIN_COREINDEX); - - if (indexUrl == null) { - throw new PluginException("no core plugin index found"); - } - - index = JAXB.unmarshal(indexUrl, PluginIndex.class); - } catch (MalformedURLException ex) { - throw new PluginException("could not load core plugin index", ex); - } catch (DataBindingException ex) { - throw new PluginException("could not unmarshall core plugin index", ex); - } - - return index; - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - private File getPluginDirectory() { - File baseDirectory = SCMContext.getContext().getBaseDirectory(); - - return new File(baseDirectory, DIRECTORY_PLUGINS); - } - - //~--- inner classes -------------------------------------------------------- - - /** - * Class description - * - * - * @version Enter version here..., 14/07/09 - * @author Enter your name here... - */ - @XmlAccessorType(XmlAccessType.FIELD) - @XmlRootElement(name = "plugin-index") - private static class PluginIndex implements Iterable { - - /** - * Method description - * - * - * @return - */ - @Override - public Iterator iterator() { - return getPlugins().iterator(); - } - - //~--- get methods -------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - public List getPlugins() { - if (plugins == null) { - plugins = ImmutableList.of(); - } - - return plugins; - } - - //~--- fields ------------------------------------------------------------- - - /** Field description */ - @XmlElement(name = "plugins") - private List plugins; - } - - - /** - * Class description - * - * - * @version Enter version here..., 14/07/09 - * @author Enter your name here... - */ - @XmlRootElement(name = "plugins") - @XmlAccessorType(XmlAccessType.FIELD) - private static class PluginIndexEntry { - - /** - * Method description - * - * - * @return - */ - public String getChecksum() { - return checksum; - } - - /** - * Method description - * - * - * @return - */ - public String getName() { - return name; - } - - //~--- fields ------------------------------------------------------------- - - /** Field description */ - private String checksum; - - /** Field description */ - private String name; - } - - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private ServletContext context; - - /** Field description */ - private ServletContextListener contextListener; - - private static class ScmContextListenerModule extends AbstractModule { - @Override - protected void configure() { - install(new FactoryModuleBuilder().build(ScmContextListener.Factory.class)); - } - } -} diff --git a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextFilter.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/BootstrapContextFilter.java similarity index 97% rename from scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextFilter.java rename to scm-webapp/src/main/java/sonia/scm/lifecycle/BootstrapContextFilter.java index a134b44784..ffb7631922 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/BootstrapContextFilter.java @@ -29,7 +29,7 @@ -package sonia.scm.boot; +package sonia.scm.lifecycle; //~--- non-JDK imports -------------------------------------------------------- @@ -82,7 +82,6 @@ public class BootstrapContextFilter extends GuiceFilter { super.destroy(); listener.contextDestroyed(new ServletContextEvent(filterConfig.getServletContext())); - ServletContextCleaner.cleanup(filterConfig.getServletContext()); } /** diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/BootstrapContextListener.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/BootstrapContextListener.java new file mode 100644 index 0000000000..f7f932634a --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/BootstrapContextListener.java @@ -0,0 +1,178 @@ +/** + * 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.lifecycle; + +import com.google.common.collect.ImmutableList; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Module; +import com.google.inject.servlet.GuiceServletContextListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.lifecycle.classloading.ClassLoaderLifeCycle; +import sonia.scm.lifecycle.modules.ApplicationModuleProvider; +import sonia.scm.lifecycle.modules.BootstrapModule; +import sonia.scm.lifecycle.modules.CloseableModule; +import sonia.scm.lifecycle.modules.EagerSingletonModule; +import sonia.scm.SCMContext; +import sonia.scm.lifecycle.modules.InjectionLifeCycle; +import sonia.scm.lifecycle.modules.ModuleProvider; +import sonia.scm.lifecycle.modules.ScmEventBusModule; +import sonia.scm.lifecycle.modules.ScmInitializerModule; +import sonia.scm.lifecycle.modules.ServletContextModule; +import sonia.scm.lifecycle.modules.UpdateStepModule; +import sonia.scm.lifecycle.view.SingleView; +import sonia.scm.plugin.PluginLoader; +import sonia.scm.update.MigrationWizardModuleProvider; +import sonia.scm.update.UpdateEngine; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Sebastian Sdorra + */ +public class BootstrapContextListener extends GuiceServletContextListener { + + private static final Logger LOG = LoggerFactory.getLogger(BootstrapContextListener.class); + + private final ClassLoaderLifeCycle classLoaderLifeCycle = ClassLoaderLifeCycle.create(); + + private ServletContext context; + private InjectionLifeCycle injectionLifeCycle; + + @Override + public void contextInitialized(ServletContextEvent sce) { + LOG.info("start scm-manager initialization"); + + context = sce.getServletContext(); + classLoaderLifeCycle.initialize(); + super.contextInitialized(sce); + + Injector injector = (Injector) context.getAttribute(Injector.class.getName()); + injectionLifeCycle = new InjectionLifeCycle(injector); + injectionLifeCycle.initialize(); + } + + @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 + public void contextDestroyed(ServletContextEvent sce) { + LOG.info("shutdown scm-manager context"); + + ServletContextCleaner.cleanup(context); + + injectionLifeCycle.shutdown(); + injectionLifeCycle = null; + classLoaderLifeCycle.shutdown(); + } + + private Injector createStageTwoInjector() { + 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()); + + return startEitherMigrationOrApplication(pluginBootstrap.getPluginLoader(), bootstrapInjector); + } + + private ModuleProvider startEitherMigrationOrApplication(PluginLoader pluginLoader, Injector bootstrapInjector) { + MigrationWizardModuleProvider wizardModuleProvider = new MigrationWizardModuleProvider(bootstrapInjector); + + if (wizardModuleProvider.wizardNecessary()) { + return wizardModuleProvider; + } else { + processUpdates(pluginLoader, bootstrapInjector); + + Versions.writeNew(); + + return new ApplicationModuleProvider(context, pluginLoader); + } + } + + private Injector createStageOneInjector(ModuleProvider provider) { + return Guice.createInjector(provider.createModules()); + } + + private Injector createStageTwoInjector(ModuleProvider provider, PluginLoader pluginLoader) { + List modules = new ArrayList<>(createBootstrapModules(pluginLoader)); + modules.addAll(provider.createModules()); + return Guice.createInjector(modules); + } + + private Injector createBootstrapInjector(PluginLoader pluginLoader) { + return Guice.createInjector(createBootstrapModules(pluginLoader)); + } + + private List createBootstrapModules(PluginLoader pluginLoader) { + List modules = new ArrayList<>(createBaseModules()); + modules.add(new BootstrapModule(pluginLoader)); + return modules; + } + + private List createBaseModules() { + return ImmutableList.of( + new EagerSingletonModule(), + new ScmInitializerModule(), + new ScmEventBusModule(), + new ServletContextModule(), + new CloseableModule() + ); + } + + private void processUpdates(PluginLoader pluginLoader, Injector bootstrapInjector) { + Injector updateInjector = bootstrapInjector.createChildInjector(new UpdateStepModule(pluginLoader)); + + UpdateEngine updateEngine = updateInjector.getInstance(UpdateEngine.class); + updateEngine.update(); + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/boot/InjectionContextRestartStrategy.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/InjectionContextRestartStrategy.java similarity index 98% rename from scm-webapp/src/main/java/sonia/scm/boot/InjectionContextRestartStrategy.java rename to scm-webapp/src/main/java/sonia/scm/lifecycle/InjectionContextRestartStrategy.java index d0b25ba5a9..683507c563 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/InjectionContextRestartStrategy.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/InjectionContextRestartStrategy.java @@ -1,4 +1,4 @@ -package sonia.scm.boot; +package sonia.scm.lifecycle; import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/LifeCycle.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/LifeCycle.java new file mode 100644 index 0000000000..c5b386808b --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/LifeCycle.java @@ -0,0 +1,9 @@ +package sonia.scm.lifecycle; + +public interface LifeCycle { + + void initialize(); + + void shutdown(); + +} diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/PluginBootstrap.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/PluginBootstrap.java new file mode 100644 index 0000000000..e19d41cc69 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/PluginBootstrap.java @@ -0,0 +1,209 @@ +package sonia.scm.lifecycle; + +import com.google.common.base.Charsets; +import com.google.common.collect.ImmutableList; +import com.google.common.io.Files; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.SCMContext; +import sonia.scm.lifecycle.classloading.ClassLoaderLifeCycle; +import sonia.scm.migration.UpdateException; +import sonia.scm.plugin.DefaultPluginLoader; +import sonia.scm.plugin.Plugin; +import sonia.scm.plugin.PluginException; +import sonia.scm.plugin.PluginLoadException; +import sonia.scm.plugin.PluginLoader; +import sonia.scm.plugin.PluginWrapper; +import sonia.scm.plugin.PluginsInternal; +import sonia.scm.plugin.SmpArchive; +import sonia.scm.util.IOUtil; + +import javax.servlet.ServletContext; +import javax.xml.bind.DataBindingException; +import javax.xml.bind.JAXB; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +public final class PluginBootstrap { + + private static final Logger LOG = LoggerFactory.getLogger(PluginBootstrap.class); + + private static final String DIRECTORY_PLUGINS = "plugins"; + private static final String PLUGIN_DIRECTORY = "/WEB-INF/plugins/"; + private static final String PLUGIN_COREINDEX = PLUGIN_DIRECTORY.concat("plugin-index.xml"); + + private final ClassLoaderLifeCycle classLoaderLifeCycle; + private final ServletContext servletContext; + private final Set plugins; + private final PluginLoader pluginLoader; + + PluginBootstrap(ServletContext servletContext, ClassLoaderLifeCycle classLoaderLifeCycle) { + this.servletContext = servletContext; + this.classLoaderLifeCycle = classLoaderLifeCycle; + + this.plugins = collectPlugins(); + this.pluginLoader = createPluginLoader(); + } + + public PluginLoader getPluginLoader() { + return pluginLoader; + } + + public Set getPlugins() { + return plugins; + } + + private PluginLoader createPluginLoader() { + return new DefaultPluginLoader(servletContext, classLoaderLifeCycle.getBootstrapClassLoader(), plugins); + } + + private Set collectPlugins() { + try { + File pluginDirectory = getPluginDirectory(); + + renameOldPluginsFolder(pluginDirectory); + + if (!isCorePluginExtractionDisabled()) { + extractCorePlugins(servletContext, pluginDirectory); + } else { + LOG.info("core plugin extraction is disabled"); + } + + return PluginsInternal.collectPlugins(classLoaderLifeCycle, pluginDirectory.toPath()); + } catch (IOException ex) { + throw new PluginLoadException("could not load plugins", ex); + } + } + + private void renameOldPluginsFolder(File pluginDirectory) { + if (new File(pluginDirectory, "classpath.xml").exists()) { + File backupDirectory = new File(pluginDirectory.getParentFile(), "plugins.v1"); + boolean renamed = pluginDirectory.renameTo(backupDirectory); + if (renamed) { + LOG.warn("moved old plugins directory to {}", backupDirectory); + } else { + throw new UpdateException("could not rename existing v1 plugin directory"); + } + } + } + + + private boolean isCorePluginExtractionDisabled() { + return Boolean.getBoolean("sonia.scm.boot.disable-core-plugin-extraction"); + } + + private void extractCorePlugin(ServletContext context, File pluginDirectory, + PluginIndexEntry entry) throws IOException { + URL url = context.getResource(PLUGIN_DIRECTORY.concat(entry.getName())); + SmpArchive archive = SmpArchive.create(url); + Plugin plugin = archive.getPlugin(); + + File directory = PluginsInternal.createPluginDirectory(pluginDirectory, plugin); + File checksumFile = PluginsInternal.getChecksumFile(directory); + + if (!directory.exists()) { + LOG.warn("install plugin {}", plugin.getInformation().getId()); + PluginsInternal.extract(archive, entry.getChecksum(), directory, checksumFile, true); + } else if (!checksumFile.exists()) { + LOG.warn("plugin directory {} exists without checksum file.", directory); + PluginsInternal.extract(archive, entry.getChecksum(), directory, checksumFile, true); + } else { + String checksum = Files.toString(checksumFile, Charsets.UTF_8).trim(); + + if (checksum.equals(entry.getChecksum())) { + LOG.debug("plugin {} is up to date", plugin.getInformation().getId()); + } else { + LOG.warn("checksum mismatch of pluing {}, start update", plugin.getInformation().getId()); + PluginsInternal.extract(archive, entry.getChecksum(), directory, checksumFile, true); + } + } + } + + private void extractCorePlugins(ServletContext context, File pluginDirectory) throws IOException { + IOUtil.mkdirs(pluginDirectory); + + PluginIndex index = readCorePluginIndex(context); + + for (PluginIndexEntry entry : index) { + extractCorePlugin(context, pluginDirectory, entry); + } + } + + + private PluginIndex readCorePluginIndex(ServletContext context) { + PluginIndex index; + + try { + URL indexUrl = context.getResource(PLUGIN_COREINDEX); + + if (indexUrl == null) { + throw new PluginException("no core plugin index found"); + } + + index = JAXB.unmarshal(indexUrl, PluginIndex.class); + } catch (MalformedURLException ex) { + throw new PluginException("could not load core plugin index", ex); + } catch (DataBindingException ex) { + throw new PluginException("could not unmarshal core plugin index", ex); + } + + return index; + } + + private File getPluginDirectory() { + File baseDirectory = SCMContext.getContext().getBaseDirectory(); + + return new File(baseDirectory, DIRECTORY_PLUGINS); + } + + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlRootElement(name = "plugin-index") + private static class PluginIndex implements Iterable { + + @XmlElement(name = "plugins") + private List plugins; + + @Override + public Iterator iterator() { + return getPlugins().iterator(); + } + + public List getPlugins() { + if (plugins == null) { + plugins = ImmutableList.of(); + } + + return plugins; + } + + } + + @XmlRootElement(name = "plugins") + @XmlAccessorType(XmlAccessType.FIELD) + private static class PluginIndexEntry { + + private String checksum; + + private String name; + + public String getName() { + return name; + } + + public String getChecksum() { + return checksum; + } + + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/boot/RestartServlet.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/RestartServlet.java similarity index 99% rename from scm-webapp/src/main/java/sonia/scm/boot/RestartServlet.java rename to scm-webapp/src/main/java/sonia/scm/lifecycle/RestartServlet.java index c7177cc459..6e70216a5c 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/RestartServlet.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/RestartServlet.java @@ -1,4 +1,4 @@ -package sonia.scm.boot; +package sonia.scm.lifecycle; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; diff --git a/scm-webapp/src/main/java/sonia/scm/boot/RestartStrategy.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/RestartStrategy.java similarity index 95% rename from scm-webapp/src/main/java/sonia/scm/boot/RestartStrategy.java rename to scm-webapp/src/main/java/sonia/scm/lifecycle/RestartStrategy.java index fa1fd052c6..769351a850 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/RestartStrategy.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/RestartStrategy.java @@ -1,4 +1,4 @@ -package sonia.scm.boot; +package sonia.scm.lifecycle; /** * Strategy for restarting SCM-Manager. diff --git a/scm-webapp/src/main/java/sonia/scm/boot/ServletContextCleaner.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/ServletContextCleaner.java similarity index 83% rename from scm-webapp/src/main/java/sonia/scm/boot/ServletContextCleaner.java rename to scm-webapp/src/main/java/sonia/scm/lifecycle/ServletContextCleaner.java index 085b752096..be386f4ac6 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/ServletContextCleaner.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/ServletContextCleaner.java @@ -1,12 +1,10 @@ -package sonia.scm.boot; +package sonia.scm.lifecycle; import com.google.common.collect.ImmutableSet; -import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.ServletContext; -import javax.ws.rs.ext.RuntimeDelegate; import java.util.Enumeration; import java.util.Set; @@ -49,9 +47,7 @@ final class ServletContextCleaner { } } - ResteasyProviderFactory.clearInstanceIfEqual(ResteasyProviderFactory.getInstance()); - ResteasyProviderFactory.clearContextData(); - RuntimeDelegate.setInstance(null); + } private static boolean shouldRemove(String name) { diff --git a/scm-webapp/src/main/java/sonia/scm/boot/SetupContextListener.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/SetupContextListener.java similarity index 98% rename from scm-webapp/src/main/java/sonia/scm/boot/SetupContextListener.java rename to scm-webapp/src/main/java/sonia/scm/lifecycle/SetupContextListener.java index 59002c2d08..bbc0dce120 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/SetupContextListener.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/SetupContextListener.java @@ -1,4 +1,4 @@ -package sonia.scm.boot; +package sonia.scm.lifecycle; import com.google.common.annotations.VisibleForTesting; import org.apache.shiro.authc.credential.PasswordService; diff --git a/scm-webapp/src/main/java/sonia/scm/boot/Versions.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/Versions.java similarity index 98% rename from scm-webapp/src/main/java/sonia/scm/boot/Versions.java rename to scm-webapp/src/main/java/sonia/scm/lifecycle/Versions.java index 6da19fedca..b482c8f8e9 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/Versions.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/Versions.java @@ -1,4 +1,4 @@ -package sonia.scm.boot; +package sonia.scm.lifecycle; import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; diff --git a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapClassLoader.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/BootstrapClassLoader.java similarity index 87% rename from scm-webapp/src/main/java/sonia/scm/boot/BootstrapClassLoader.java rename to scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/BootstrapClassLoader.java index cc7b807137..64d9b75d36 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapClassLoader.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/BootstrapClassLoader.java @@ -1,4 +1,4 @@ -package sonia.scm.boot; +package sonia.scm.lifecycle.classloading; /** * This ClassLoader is mainly a wrapper around the web application class loader and its goal is to make it easier to diff --git a/scm-webapp/src/main/java/sonia/scm/boot/ClassLoaderLifeCycle.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycle.java similarity index 95% rename from scm-webapp/src/main/java/sonia/scm/boot/ClassLoaderLifeCycle.java rename to scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycle.java index be1d4d3653..6f9a8610c4 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/ClassLoaderLifeCycle.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycle.java @@ -1,4 +1,4 @@ -package sonia.scm.boot; +package sonia.scm.lifecycle.classloading; import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; @@ -6,6 +6,7 @@ import org.slf4j.LoggerFactory; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventorFactory; import se.jiderhamn.classloader.leak.prevention.cleanup.MBeanCleanUp; +import sonia.scm.lifecycle.LifeCycle; import sonia.scm.plugin.ChildFirstPluginClassLoader; import sonia.scm.plugin.DefaultPluginClassLoader; @@ -21,7 +22,7 @@ import static com.google.common.base.Preconditions.checkState; /** * Creates and shutdown SCM-Manager ClassLoaders. */ -public final class ClassLoaderLifeCycle { +public final class ClassLoaderLifeCycle implements LifeCycle { private static final Logger LOG = LoggerFactory.getLogger(ClassLoaderLifeCycle.class); @@ -46,7 +47,7 @@ public final class ClassLoaderLifeCycle { this.webappClassLoader = initAndAppend(webappClassLoader); } - void init() { + public void initialize() { bootstrapClassLoader = initAndAppend(new BootstrapClassLoader(webappClassLoader)); } @@ -72,7 +73,7 @@ public final class ClassLoaderLifeCycle { return initAndAppend(pluginClassLoader); } - void shutdown() { + public void shutdown() { LOG.info("shutdown classloader infrastructure"); ClassLoaderAndPreventor clap = classLoaders.poll(); while (clap != null) { diff --git a/scm-webapp/src/main/java/sonia/scm/boot/LoggingAdapter.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/LoggingAdapter.java similarity index 95% rename from scm-webapp/src/main/java/sonia/scm/boot/LoggingAdapter.java rename to scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/LoggingAdapter.java index cc7f6befea..d4dbde2ef1 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/LoggingAdapter.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/LoggingAdapter.java @@ -1,4 +1,4 @@ -package sonia.scm.boot; +package sonia.scm.lifecycle.classloading; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ApplicationModuleProvider.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ApplicationModuleProvider.java new file mode 100644 index 0000000000..62793da0f6 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ApplicationModuleProvider.java @@ -0,0 +1,74 @@ +package sonia.scm.lifecycle.modules; + +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.SCMContext; +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; + +public class ApplicationModuleProvider implements ModuleProvider { + + private static final Logger LOG = LoggerFactory.getLogger(ApplicationModuleProvider.class); + + private final ServletContext servletContext; + private final PluginLoader pluginLoader; + + public ApplicationModuleProvider(ServletContext servletContext, PluginLoader pluginLoader) { + this.servletContext = servletContext; + this.pluginLoader = pluginLoader; + } + + @Override + public List createModules() { + ClassOverrides overrides = createClassOverrides(); + return createModules(overrides); + } + + private List createModules(ClassOverrides overrides) { + List 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 moduleList) { + for (Class module : ep.byExtensionPoint(Module.class)) { + try { + LOG.info("add module {}", module); + moduleList.add(module.newInstance()); + } catch (IllegalAccessException | InstantiationException ex) { + throw Throwables.propagate(ex); + } + } + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapModule.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/BootstrapModule.java similarity index 96% rename from scm-webapp/src/main/java/sonia/scm/boot/BootstrapModule.java rename to scm-webapp/src/main/java/sonia/scm/lifecycle/modules/BootstrapModule.java index 4765d9b96e..9b6ea719d8 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapModule.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/BootstrapModule.java @@ -1,10 +1,9 @@ -package sonia.scm.boot; +package sonia.scm.lifecycle.modules; import com.google.inject.AbstractModule; import com.google.inject.throwingproviders.ThrowingProviderBinder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.ClassOverrides; import sonia.scm.SCMContext; import sonia.scm.SCMContextProvider; import sonia.scm.io.DefaultFileSystem; @@ -36,7 +35,7 @@ public class BootstrapModule extends AbstractModule { private final ClassOverrides overrides; private final PluginLoader pluginLoader; - BootstrapModule(PluginLoader pluginLoader) { + public BootstrapModule(PluginLoader pluginLoader) { this.overrides = ClassOverrides.findOverrides(pluginLoader.getUberClassLoader()); this.pluginLoader = pluginLoader; } diff --git a/scm-webapp/src/main/java/sonia/scm/ClassOverride.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ClassOverride.java similarity index 98% rename from scm-webapp/src/main/java/sonia/scm/ClassOverride.java rename to scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ClassOverride.java index 8741ce85af..779a62b6e0 100644 --- a/scm-webapp/src/main/java/sonia/scm/ClassOverride.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ClassOverride.java @@ -30,12 +30,13 @@ */ -package sonia.scm; +package sonia.scm.lifecycle.modules; //~--- non-JDK imports -------------------------------------------------------- import com.google.common.base.MoreObjects; import com.google.common.base.Objects; +import sonia.scm.Validateable; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; diff --git a/scm-webapp/src/main/java/sonia/scm/ClassOverrides.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ClassOverrides.java similarity index 99% rename from scm-webapp/src/main/java/sonia/scm/ClassOverrides.java rename to scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ClassOverrides.java index b727a75fc0..d37bb989b7 100644 --- a/scm-webapp/src/main/java/sonia/scm/ClassOverrides.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ClassOverrides.java @@ -31,7 +31,7 @@ -package sonia.scm; +package sonia.scm.lifecycle.modules; //~--- non-JDK imports -------------------------------------------------------- diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/CloseableModule.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/CloseableModule.java new file mode 100644 index 0000000000..981de6e2e0 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/CloseableModule.java @@ -0,0 +1,65 @@ +package sonia.scm.lifecycle.modules; + +import com.google.inject.AbstractModule; +import com.google.inject.TypeLiteral; +import com.google.inject.spi.InjectionListener; +import com.google.inject.spi.TypeEncounter; +import com.google.inject.spi.TypeListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.util.IOUtil; + +import java.io.Closeable; +import java.lang.ref.WeakReference; +import java.util.Deque; +import java.util.concurrent.ConcurrentLinkedDeque; + +/** + * Guice module which captures all classes which are implementing the {@link Closeable}. These classes can be later + * closed, by injecting the {@link CloseableModule} and calling {@link #closeAll()}. + * + * @author Sebastian Sdorra + */ +public final class CloseableModule extends AbstractModule { + + private static final Logger LOG = LoggerFactory.getLogger(CloseableModule.class); + + private final Deque> closeables = new ConcurrentLinkedDeque<>(); + + @Override + protected void configure() { + bindListener(MoreMatchers.isSubtypeOf(Closeable.class), new TypeListener() { + @Override + public void hear(TypeLiteral type, TypeEncounter encounter) { + encounter.register((InjectionListener) instance -> { + LOG.debug("register closable {}", instance.getClass()); + Closeable closeable = (Closeable) instance; + closeables.push(new WeakReference<>(closeable)); + }); + } + }); + + bind(CloseableModule.class).toInstance(this); + } + + /** + * Closes all captured instances. + */ + public void closeAll() { + LOG.debug("close all registered closeables"); + WeakReference reference = closeables.poll(); + while (reference != null) { + Closeable closeable = reference.get(); + close(closeable); + reference = closeables.poll(); + } + } + + private void close(Closeable closeable) { + if (closeable != null) { + LOG.trace("close closeable instance of {}", closeable); + IOUtil.close(closeable); + } + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/EagerSingletonModule.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/EagerSingletonModule.java similarity index 53% rename from scm-webapp/src/main/java/sonia/scm/EagerSingletonModule.java rename to scm-webapp/src/main/java/sonia/scm/lifecycle/modules/EagerSingletonModule.java index fd519beb31..4feee1c4a6 100644 --- a/scm-webapp/src/main/java/sonia/scm/EagerSingletonModule.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/EagerSingletonModule.java @@ -1,19 +1,19 @@ /** * 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. + * 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. + * 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. - * + * 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 @@ -24,13 +24,12 @@ * 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; +package sonia.scm.lifecycle.modules; //~--- non-JDK imports -------------------------------------------------------- @@ -38,96 +37,52 @@ import com.google.common.collect.Sets; import com.google.inject.AbstractModule; import com.google.inject.Injector; import com.google.inject.TypeLiteral; -import com.google.inject.matcher.AbstractMatcher; -import com.google.inject.matcher.Matcher; import com.google.inject.spi.TypeEncounter; import com.google.inject.spi.TypeListener; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -//~--- JDK imports ------------------------------------------------------------ - -import java.lang.annotation.Annotation; +import sonia.scm.EagerSingleton; import java.util.Set; +//~--- JDK imports ------------------------------------------------------------ + /** + * Guice module which captures all classes which are annotated with {@link EagerSingleton}. These classes can be later + * initialized. * * @author Sebastian Sdorra */ -public class EagerSingletonModule extends AbstractModule -{ +public class EagerSingletonModule extends AbstractModule { + + private static final Logger LOG = LoggerFactory.getLogger(EagerSingletonModule.class); + + private final Set> eagerSingletons = Sets.newHashSet(); /** - * the logger for EagerSingletonModule - */ - private static final Logger logger = - LoggerFactory.getLogger(EagerSingletonModule.class); - - //~--- methods -------------------------------------------------------------- - - /** - * Method description + * Initialize all captured classes. * - * - * @param injector + * @param injector injector for initialization */ - void initialize(Injector injector) - { - for (Class clazz : eagerSingletons) - { - logger.info("initialize eager singleton {}", clazz.getName()); + void initialize(Injector injector) { + for (Class clazz : eagerSingletons) { + LOG.info("initialize eager singleton {}", clazz.getName()); injector.getInstance(clazz); } } - /** - * Method description - * - */ @Override - protected void configure() - { - bindListener(isAnnotatedWith(EagerSingleton.class), new TypeListener() - { - + protected void configure() { + bindListener(MoreMatchers.isAnnotatedWith(EagerSingleton.class), new TypeListener() { @Override - public void hear(TypeLiteral type, TypeEncounter encounter) - { - eagerSingletons.add(type.getRawType()); + public void hear(TypeLiteral type, TypeEncounter encounter) { + Class rawType = type.getRawType(); + LOG.trace("register eager singleton {}", rawType); + eagerSingletons.add(rawType); } }); bind(EagerSingletonModule.class).toInstance(this); } - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * - * @param annotation - * - * @return - */ - private Matcher> isAnnotatedWith( - final Class annotation) - { - return new AbstractMatcher>() - { - @Override - public boolean matches(TypeLiteral type) - { - return type.getRawType().isAnnotationPresent(annotation); - } - }; - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private final Set> eagerSingletons = Sets.newHashSet(); } diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/InjectionLifeCycle.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/InjectionLifeCycle.java new file mode 100644 index 0000000000..0322bdd71b --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/InjectionLifeCycle.java @@ -0,0 +1,58 @@ +package sonia.scm.lifecycle.modules; + +import com.google.inject.Binding; +import com.google.inject.Injector; +import com.google.inject.Key; +import sonia.scm.Default; +import sonia.scm.lifecycle.LifeCycle; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import java.util.Optional; + +public class InjectionLifeCycle implements LifeCycle { + + private final Injector injector; + + public InjectionLifeCycle(Injector injector) { + this.injector = injector; + } + + public void initialize() { + initializeEagerSingletons(); + initializeServletContextListeners(); + } + + public 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 Optional findInstance(Class clazz) { + Binding binding = injector.getExistingBinding(Key.get(clazz)); + if (binding != null) { + return Optional.of(binding.getProvider().get()); + } + return Optional.empty(); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ModuleProvider.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ModuleProvider.java new file mode 100644 index 0000000000..93517b21a1 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ModuleProvider.java @@ -0,0 +1,11 @@ +package sonia.scm.lifecycle.modules; + +import com.google.inject.Module; + +import java.util.Collection; + +public interface ModuleProvider { + + Collection createModules(); + +} diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/MoreMatchers.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/MoreMatchers.java new file mode 100644 index 0000000000..04283dcb43 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/MoreMatchers.java @@ -0,0 +1,60 @@ +package sonia.scm.lifecycle.modules; + +import com.google.inject.TypeLiteral; +import com.google.inject.matcher.AbstractMatcher; +import com.google.inject.matcher.Matcher; + +import java.lang.annotation.Annotation; + +/** + * Helper methods for Guice matchers, which are not already provided in {@link com.google.inject.matcher.Matchers}. + */ +@SuppressWarnings("unchecked") +final class MoreMatchers { + + private MoreMatchers() {} + + /** + * Returns a matcher which matches TypeListerals which are annotated with the given annotation. + * + * @param annotation annotation to match + * + * @return annotation matcher + */ + static Matcher isAnnotatedWith(final Class annotation) { + return new AbstractMatcher() { + @Override + public boolean matches(TypeLiteral type) { + return type.getRawType().isAnnotationPresent(annotation); + } + }; + } + + /** + * Returns a matcher which maches TypeLiterals which are sub types of the given class. + * + * @param supertype sub type to match + * + * @return sub type matcher + */ + static Matcher isSubtypeOf(final Class supertype) { + return isSubtypeOf(TypeLiteral.get(supertype)); + } + + private static Matcher isSubtypeOf(final TypeLiteral supertype) { + return new AbstractMatcher() { + @Override + public boolean matches(TypeLiteral type) { + return typeIsSubtypeOf(type, supertype); + } + }; + } + + private static boolean typeIsSubtypeOf(TypeLiteral subtype, TypeLiteral supertype) { + // First check that raw types are compatible + // Then check that generic types are compatible! HOW???? + return (subtype.equals(supertype) + || (supertype.getRawType().isAssignableFrom(subtype.getRawType()) + && supertype.equals(subtype.getSupertype(supertype.getRawType())))); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ResteasyAllInOneServletDispatcher.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ResteasyAllInOneServletDispatcher.java new file mode 100644 index 0000000000..6196fd7065 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ResteasyAllInOneServletDispatcher.java @@ -0,0 +1,76 @@ +package sonia.scm.lifecycle.modules; + +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; +import javax.ws.rs.ext.RuntimeDelegate; + +/** + * 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(); + + // ensure everything gets cleared, to avoid classloader leaks + ResteasyProviderFactory.clearInstanceIfEqual(ResteasyProviderFactory.getInstance()); + ResteasyProviderFactory.clearContextData(); + RuntimeDelegate.setInstance(null); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/ResteasyModule.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ResteasyModule.java similarity index 63% rename from scm-webapp/src/main/java/sonia/scm/ResteasyModule.java rename to scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ResteasyModule.java index a85c3b6d06..eac4926a57 100644 --- a/scm-webapp/src/main/java/sonia/scm/ResteasyModule.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ResteasyModule.java @@ -1,20 +1,18 @@ -package sonia.scm; +package sonia.scm.lifecycle.modules; import com.google.common.collect.ImmutableMap; import com.google.inject.servlet.ServletModule; -import org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher; import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters; -import javax.inject.Singleton; import java.util.Map; +/** + * Module to configure resteasy with guice. + */ public class ResteasyModule extends ServletModule { - @Override protected void configureServlets() { - bind(HttpServletDispatcher.class).in(Singleton.class); - Map initParams = ImmutableMap.of(ResteasyContextParameters.RESTEASY_SERVLET_MAPPING_PREFIX, "/api"); - serve("/api/*").with(HttpServletDispatcher.class, initParams); + serve("/api/*").with(ResteasyAllInOneServletDispatcher.class, initParams); } } diff --git a/scm-webapp/src/main/java/sonia/scm/ScmEventBusModule.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ScmEventBusModule.java similarity index 65% rename from scm-webapp/src/main/java/sonia/scm/ScmEventBusModule.java rename to scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ScmEventBusModule.java index 4f1d4ca8ab..a4916be318 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmEventBusModule.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ScmEventBusModule.java @@ -1,19 +1,19 @@ /** * 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. + * 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. + * 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. - * + * 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 @@ -24,56 +24,49 @@ * 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; +package sonia.scm.lifecycle.modules; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.annotations.VisibleForTesting; import com.google.inject.AbstractModule; import com.google.inject.TypeLiteral; import com.google.inject.matcher.Matchers; import com.google.inject.spi.InjectionListener; import com.google.inject.spi.TypeEncounter; import com.google.inject.spi.TypeListener; - import sonia.scm.event.ScmEventBus; /** + * Registers every instance to the scm-manager event bus. * * @author Sebastian Sdorra */ -public class ScmEventBusModule extends AbstractModule -{ +public class ScmEventBusModule extends AbstractModule { + + private final ScmEventBus eventBus; + + public ScmEventBusModule() { + this(ScmEventBus.getInstance()); + } + + @VisibleForTesting + ScmEventBusModule(ScmEventBus eventBus) { + this.eventBus = eventBus; + } - /** - * Method description - * - */ @Override - protected void configure() - { - bindListener(Matchers.any(), new TypeListener() - { - + protected void configure() { + bindListener(Matchers.any(), new TypeListener() { @Override - public void hear(TypeLiteral type, TypeEncounter encounter) - { - encounter.register(new InjectionListener() - { - @Override - public void afterInjection(Object object) - { - ScmEventBus.getInstance().register(object); - } - }); + public void hear(TypeLiteral type, TypeEncounter encounter) { + encounter.register((InjectionListener) object -> eventBus.register(object)); } - }); } } diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ScmInitializerModule.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ScmInitializerModule.java new file mode 100644 index 0000000000..b3a4cb6209 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ScmInitializerModule.java @@ -0,0 +1,71 @@ +/** + * 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.lifecycle.modules; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.inject.AbstractModule; +import com.google.inject.TypeLiteral; +import com.google.inject.spi.InjectionListener; +import com.google.inject.spi.TypeEncounter; +import com.google.inject.spi.TypeListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.Initable; +import sonia.scm.SCMContext; + +/** + * Initializes all instances which are implementing the {@link Initable} interface. + * + * @author Sebastian Sdorra + */ +public class ScmInitializerModule extends AbstractModule { + + private static final Logger LOG = LoggerFactory.getLogger(ScmInitializerModule.class); + + @Override + protected void configure() { + bindListener(MoreMatchers.isSubtypeOf(Initable.class), new TypeListener() { + @Override + public void hear(TypeLiteral type, TypeEncounter encounter) { + encounter.register((InjectionListener) i -> { + LOG.trace("initialize initable {}", i.getClass()); + + Initable initable = (Initable) i; + + initable.init(SCMContext.getContext()); + }); + } + }); + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/ScmSecurityModule.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ScmSecurityModule.java similarity index 96% rename from scm-webapp/src/main/java/sonia/scm/ScmSecurityModule.java rename to scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ScmSecurityModule.java index 527a8977af..a77f603210 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmSecurityModule.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ScmSecurityModule.java @@ -31,7 +31,7 @@ -package sonia.scm; +package sonia.scm.lifecycle.modules; //~--- non-JDK imports -------------------------------------------------------- @@ -48,8 +48,6 @@ import org.slf4j.LoggerFactory; import sonia.scm.plugin.ExtensionProcessor; -import static org.apache.shiro.guice.web.ShiroWebModule.ROLES; - //~--- JDK imports ------------------------------------------------------------ import javax.servlet.ServletContext; @@ -81,8 +79,7 @@ public class ScmSecurityModule extends ShiroWebModule * @param servletContext * @param extensionProcessor */ - ScmSecurityModule(ServletContext servletContext, - ExtensionProcessor extensionProcessor) + public ScmSecurityModule(ServletContext servletContext, ExtensionProcessor extensionProcessor) { super(servletContext); this.extensionProcessor = extensionProcessor; diff --git a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ScmServletModule.java similarity index 67% rename from scm-webapp/src/main/java/sonia/scm/ScmServletModule.java rename to scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ScmServletModule.java index 49ac0a3d59..7b8af8fbcc 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ScmServletModule.java @@ -31,7 +31,7 @@ -package sonia.scm; +package sonia.scm.lifecycle.modules; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.inject.Provider; @@ -41,6 +41,10 @@ import com.google.inject.servlet.ServletModule; import com.google.inject.throwingproviders.ThrowingProviderBinder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.Default; +import sonia.scm.PushStateDispatcher; +import sonia.scm.PushStateDispatcherProvider; +import sonia.scm.Undecorated; import sonia.scm.api.rest.ObjectMapperProvider; import sonia.scm.cache.CacheManager; import sonia.scm.cache.GuavaCacheManager; @@ -59,8 +63,8 @@ import sonia.scm.net.ahc.ContentTransformer; import sonia.scm.net.ahc.DefaultAdvancedHttpClient; import sonia.scm.net.ahc.JsonContentTransformer; import sonia.scm.net.ahc.XmlContentTransformer; -import sonia.scm.plugin.DefaultPluginLoader; import sonia.scm.plugin.DefaultPluginManager; +import sonia.scm.plugin.PluginLoader; import sonia.scm.plugin.PluginManager; import sonia.scm.repository.DefaultRepositoryManager; import sonia.scm.repository.DefaultRepositoryProvider; @@ -110,91 +114,30 @@ import sonia.scm.web.security.AdministrationContext; import sonia.scm.web.security.DefaultAdministrationContext; import javax.net.ssl.SSLContext; -import javax.servlet.ServletContext; - -import static sonia.scm.api.v2.resources.ScmPathInfo.REST_API_PATH; /** * * @author Sebastian Sdorra */ -public class ScmServletModule extends ServletModule -{ +class ScmServletModule extends ServletModule { - /** Field description */ - public static final String[] PATTERN_ADMIN = new String[] { - REST_API_PATH + "/groups*", - REST_API_PATH + "/users*", REST_API_PATH + "/plguins*" }; + private static final String PATTERN_ALL = "/*"; + private static final String PATTERN_DEBUG = "/debug.html"; + private static final String PATTERN_INDEX = "/index.html"; + private static final String SYSTEM_PROPERTY_DEBUG_HTTP = "scm.debug.http"; - /** Field description */ - public static final String PATTERN_ALL = "/*"; + private static final Logger logger = LoggerFactory.getLogger(ScmServletModule.class); - /** Field description */ - public static final String PATTERN_CONFIG = REST_API_PATH + "/config*"; + private final ClassOverrides overrides; + private final PluginLoader pluginLoader; - /** Field description */ - public static final String PATTERN_DEBUG = "/debug.html"; - - /** Field description */ - public static final String PATTERN_INDEX = "/index.html"; - - /** Field description */ - public static final String PATTERN_PAGE = "*.html"; - - /** Field description */ - public static final String PATTERN_PLUGIN_SCRIPT = "/plugins/resources/js/*"; - - /** Field description */ - public static final String PATTERN_RESTAPI = "/api/*"; - - /** Field description */ - public static final String PATTERN_SCRIPT = "*.js"; - - /** Field description */ - public static final String PATTERN_STYLESHEET = "*.css"; - - /** Field description */ - public static final String RESOURCE_REGEX = - "^/(?:resources|api|plugins|index)[\\./].*(?:html|\\.css|\\.js|\\.xml|\\.json|\\.txt)"; - - /** Field description */ - public static final String REST_PACKAGE = "sonia.scm.api.rest"; - - /** Field description */ - public static final String SYSTEM_PROPERTY_DEBUG_HTTP = "scm.debug.http"; - - /** Field description */ - public static final String[] PATTERN_STATIC_RESOURCES = new String[] { - PATTERN_SCRIPT, - PATTERN_STYLESHEET, "*.jpg", "*.gif", "*.png" }; - - /** Field description */ - public static final String[] PATTERN_COMPRESSABLE = new String[] { - PATTERN_SCRIPT, - PATTERN_STYLESHEET, "*.json", "*.xml", "*.txt" }; - - /** Field description */ - private static final Logger logger = - LoggerFactory.getLogger(ScmServletModule.class); - - //~--- constructors --------------------------------------------------------- - - ScmServletModule(ServletContext servletContext, DefaultPluginLoader pluginLoader, ClassOverrides overrides) - { - this.servletContext = servletContext; + ScmServletModule(PluginLoader pluginLoader, ClassOverrides overrides) { this.pluginLoader = pluginLoader; this.overrides = overrides; } - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - */ @Override - protected void configureServlets() - { + protected void configureServlets() { install(ThrowingProviderBinder.forModule(this)); ScmConfiguration config = getScmConfiguration(); @@ -202,13 +145,10 @@ public class ScmServletModule extends ServletModule bind(NamespaceStrategy.class).toProvider(NamespaceStrategyProvider.class); // bind repository provider - ThrowingProviderBinder.create(binder()).bind( - RepositoryProvider.class, Repository.class).to( - DefaultRepositoryProvider.class).in(RequestScoped.class); - - // bind servlet context - bind(ServletContext.class).annotatedWith(Default.class).toInstance( - servletContext); + ThrowingProviderBinder.create(binder()) + .bind(RepositoryProvider.class, Repository.class) + .to(DefaultRepositoryProvider.class) + .in(RequestScoped.class); // bind event api bind(ScmEventBus.class).toInstance(ScmEventBus.getInstance()); @@ -276,8 +216,7 @@ public class ScmServletModule extends ServletModule bind(UserAgentParser.class); // bind debug logging filter - if ("true".equalsIgnoreCase(System.getProperty(SYSTEM_PROPERTY_DEBUG_HTTP))) - { + if ("true".equalsIgnoreCase(System.getProperty(SYSTEM_PROPERTY_DEBUG_HTTP))) { filter(PATTERN_ALL).through(LoggingFilter.class); } @@ -302,114 +241,41 @@ public class ScmServletModule extends ServletModule bind(PushStateDispatcher.class).toProvider(PushStateDispatcherProvider.class); } - - /** - * Method description - * - * - * @param clazz - * @param defaultImplementation - * @param - */ - private void bind(Class clazz, - Class defaultImplementation) - { + private void bind(Class clazz, Class defaultImplementation) { Class implementation = find(clazz, defaultImplementation); - - if (logger.isDebugEnabled()) - { - logger.debug("bind {} to {}", clazz, implementation); - } - + logger.debug("bind {} to {}", clazz, implementation); bind(clazz).to(implementation); } - /** - * Method description - * - * - * @param clazz - * @param defaultImplementation - * @param providerClass - * @param - */ - private void bindDecorated(Class clazz, - Class defaultImplementation, - Class> providerClass) - { + private void bindDecorated( + Class clazz, Class defaultImplementation, Class> providerClass + ) { Class implementation = find(clazz, defaultImplementation); - if (logger.isDebugEnabled()) - { - logger.debug("bind undecorated {} to {}", clazz, implementation); - } - + logger.debug("bind undecorated {} to {}", clazz, implementation); bind(clazz).annotatedWith(Undecorated.class).to(implementation); - if (logger.isDebugEnabled()) - { - logger.debug("bind {} to provider {}", clazz, providerClass); - } - + logger.debug("bind {} to provider {}", clazz, providerClass); bind(clazz).toProvider(providerClass); } - /** - * Method description - * - * - * @param clazz - * @param defaultImplementation - * @param - * - * @return - */ - private Class find(Class clazz, - Class defaultImplementation) - { + private Class find(Class clazz, Class defaultImplementation) { Class implementation = overrides.getOverride(clazz); - if (implementation != null) - { + if (implementation != null) { logger.info("found override {} for {}", implementation, clazz); - } - else - { + } else { implementation = defaultImplementation; - - if (logger.isTraceEnabled()) - { - logger.trace( - "no override available for {}, using default implementation {}", - clazz, implementation); - } + logger.trace("no override available for {}, using default implementation {}", clazz, implementation); } return implementation; } - //~--- get methods ---------------------------------------------------------- - - /** - * Load ScmConfiguration with JAXB - */ - private ScmConfiguration getScmConfiguration() - { + private ScmConfiguration getScmConfiguration() { ScmConfiguration configuration = new ScmConfiguration(); - ScmConfigurationUtil.getInstance().load(configuration); - return configuration; } - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private final ClassOverrides overrides; - - /** Field description */ - private final DefaultPluginLoader pluginLoader; - - /** Field description */ - private final ServletContext servletContext; } diff --git a/scm-webapp/src/main/java/sonia/scm/ServletContextListenerHolder.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ServletContextListenerHolder.java similarity index 70% rename from scm-webapp/src/main/java/sonia/scm/ServletContextListenerHolder.java rename to scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ServletContextListenerHolder.java index e55592c0ba..cebdcd33b0 100644 --- a/scm-webapp/src/main/java/sonia/scm/ServletContextListenerHolder.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ServletContextListenerHolder.java @@ -30,7 +30,7 @@ */ -package sonia.scm; +package sonia.scm.lifecycle.modules; //~--- non-JDK imports -------------------------------------------------------- @@ -39,6 +39,7 @@ import com.google.inject.Singleton; //~--- JDK imports ------------------------------------------------------------ +import java.util.Collections; import java.util.Set; import javax.servlet.ServletContextEvent; @@ -49,55 +50,36 @@ import javax.servlet.ServletContextListener; * @author Sebastian Sdorra */ @Singleton -public class ServletContextListenerHolder implements ServletContextListener -{ +public class ServletContextListenerHolder implements ServletContextListener { + + static class ListenerHolder { + @Inject(optional = true) + private Set listenerSet; + + private Set getListenerSet() { + if (listenerSet == null) { + return Collections.emptySet(); + } + return listenerSet; + } + } + + private final Set listenerSet; - /** - * Constructs ... - * - * - * @param listenerSet - */ @Inject - public ServletContextListenerHolder(Set listenerSet) + public ServletContextListenerHolder(ListenerHolder listeners) { - this.listenerSet = listenerSet; + this.listenerSet = listeners.getListenerSet(); } - //~--- methods -------------------------------------------------------------- - /** - * Method description - * - * - * @param sce - */ @Override - public void contextDestroyed(ServletContextEvent sce) - { - for (ServletContextListener listener : listenerSet) - { - listener.contextDestroyed(sce); - } + public void contextInitialized(ServletContextEvent sce) { + listenerSet.forEach(listener -> listener.contextInitialized(sce)); } - /** - * Method description - * - * - * @param sce - */ @Override - public void contextInitialized(ServletContextEvent sce) - { - for (ServletContextListener listener : listenerSet) - { - listener.contextInitialized(sce); - } + public void contextDestroyed(ServletContextEvent sce) { + listenerSet.forEach(listener -> listener.contextDestroyed(sce)); } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private Set listenerSet; } diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ServletContextModule.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ServletContextModule.java new file mode 100644 index 0000000000..6173d0a83f --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ServletContextModule.java @@ -0,0 +1,14 @@ +package sonia.scm.lifecycle.modules; + +import com.google.inject.servlet.ServletModule; +import sonia.scm.Default; + +import javax.servlet.ServletContext; + +public class ServletContextModule extends ServletModule { + + @Override + protected void configureServlets() { + bind(ServletContext.class).annotatedWith(Default.class).toInstance(getServletContext()); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/boot/UpdateStepModule.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/UpdateStepModule.java similarity index 79% rename from scm-webapp/src/main/java/sonia/scm/boot/UpdateStepModule.java rename to scm-webapp/src/main/java/sonia/scm/lifecycle/modules/UpdateStepModule.java index f5f74058b1..964af69c02 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/UpdateStepModule.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/UpdateStepModule.java @@ -1,15 +1,15 @@ -package sonia.scm.boot; +package sonia.scm.lifecycle.modules; import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; import sonia.scm.migration.UpdateStep; import sonia.scm.plugin.PluginLoader; -class UpdateStepModule extends AbstractModule { +public class UpdateStepModule extends AbstractModule { private final PluginLoader pluginLoader; - UpdateStepModule(PluginLoader pluginLoader) { + public UpdateStepModule(PluginLoader pluginLoader) { this.pluginLoader = pluginLoader; } diff --git a/scm-webapp/src/main/java/sonia/scm/boot/SingleView.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/view/SingleView.java similarity index 76% rename from scm-webapp/src/main/java/sonia/scm/boot/SingleView.java rename to scm-webapp/src/main/java/sonia/scm/lifecycle/view/SingleView.java index 72e2c3522f..3ac5d71e70 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/SingleView.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/view/SingleView.java @@ -1,30 +1,31 @@ -package sonia.scm.boot; +package sonia.scm.lifecycle.view; import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.inject.Guice; -import com.google.inject.Injector; +import com.google.inject.Module; import com.google.inject.multibindings.Multibinder; -import com.google.inject.servlet.GuiceServletContextListener; import com.google.inject.servlet.ServletModule; import sonia.scm.Default; import sonia.scm.SCMContext; import sonia.scm.SCMContextProvider; +import sonia.scm.StaticResourceServlet; +import sonia.scm.lifecycle.modules.ModuleProvider; +import sonia.scm.lifecycle.modules.ServletContextModule; import sonia.scm.template.MustacheTemplateEngine; import sonia.scm.template.TemplateEngine; import sonia.scm.template.TemplateEngineFactory; -import javax.servlet.ServletContext; -import javax.servlet.ServletContextListener; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.util.Collection; -final class SingleView { +public final class SingleView { private SingleView() { } - static ServletContextListener error(Throwable throwable) { + public static SingleViewModuleProvider error(Throwable throwable) { String error = Throwables.getStackTraceAsString(throwable); ViewController controller = new SimpleViewController("/templates/error.mustache", request -> { @@ -34,30 +35,30 @@ final class SingleView { ); return new View(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, model); }); - return new SingleViewContextListener(controller); + return new SingleViewModuleProvider(controller); } - static ServletContextListener view(String template, int sc) { + public static SingleViewModuleProvider view(String template, int sc) { ViewController controller = new SimpleViewController(template, request -> { Object model = ImmutableMap.of( "contextPath", request.getContextPath() ); 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 SingleViewContextListener(ViewController controller) { + private SingleViewModuleProvider(ViewController controller) { this.controller = controller; } @Override - protected Injector getInjector() { - return Guice.createInjector(new SingleViewModule(controller)); + public Collection createModules() { + return ImmutableList.of(new ServletContextModule(), new SingleViewModule(controller)); } } @@ -84,8 +85,6 @@ final class SingleView { MustacheTemplateEngine.class); bind(TemplateEngineFactory.class); - bind(ServletContext.class).annotatedWith(Default.class).toInstance(getServletContext()); - serve("/images/*", "/styles/*", "/favicon.ico").with(StaticResourceServlet.class); serve("/*").with(SingleViewServlet.class); } diff --git a/scm-webapp/src/main/java/sonia/scm/boot/SingleViewServlet.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/view/SingleViewServlet.java similarity index 98% rename from scm-webapp/src/main/java/sonia/scm/boot/SingleViewServlet.java rename to scm-webapp/src/main/java/sonia/scm/lifecycle/view/SingleViewServlet.java index 3621105976..f93228e2bc 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/SingleViewServlet.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/view/SingleViewServlet.java @@ -1,4 +1,4 @@ -package sonia.scm.boot; +package sonia.scm.lifecycle.view; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/scm-webapp/src/main/java/sonia/scm/boot/View.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/view/View.java similarity index 89% rename from scm-webapp/src/main/java/sonia/scm/boot/View.java rename to scm-webapp/src/main/java/sonia/scm/lifecycle/view/View.java index 6e1f93bd3f..a89d682e04 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/View.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/view/View.java @@ -1,4 +1,4 @@ -package sonia.scm.boot; +package sonia.scm.lifecycle.view; class View { diff --git a/scm-webapp/src/main/java/sonia/scm/boot/ViewController.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/view/ViewController.java similarity index 82% rename from scm-webapp/src/main/java/sonia/scm/boot/ViewController.java rename to scm-webapp/src/main/java/sonia/scm/lifecycle/view/ViewController.java index 26f463f9c2..3111d11651 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/ViewController.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/view/ViewController.java @@ -1,4 +1,4 @@ -package sonia.scm.boot; +package sonia.scm.lifecycle.view; import javax.servlet.http.HttpServletRequest; diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java index f75329cf65..c7d669ee63 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java @@ -41,7 +41,7 @@ import com.google.common.collect.Sets; import com.google.common.hash.Hashing; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.boot.ClassLoaderLifeCycle; +import sonia.scm.lifecycle.classloading.ClassLoaderLifeCycle; import sonia.scm.plugin.ExplodedSmp.PathTransformer; import javax.xml.bind.JAXBContext; diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginsInternal.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginsInternal.java index 07d0fe4ee9..52d192da32 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginsInternal.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginsInternal.java @@ -41,7 +41,7 @@ import com.google.common.io.Files; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.boot.ClassLoaderLifeCycle; +import sonia.scm.lifecycle.classloading.ClassLoaderLifeCycle; import sonia.scm.util.IOUtil; //~--- JDK imports ------------------------------------------------------------ diff --git a/scm-webapp/src/main/java/sonia/scm/update/MigrationWizardModule.java b/scm-webapp/src/main/java/sonia/scm/update/MigrationWizardModule.java index 4b357b6d96..465b0595f7 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/MigrationWizardModule.java +++ b/scm-webapp/src/main/java/sonia/scm/update/MigrationWizardModule.java @@ -3,8 +3,7 @@ package sonia.scm.update; import com.google.inject.servlet.ServletModule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.PushStateDispatcher; -import sonia.scm.WebResourceServlet; +import sonia.scm.StaticResourceServlet; class MigrationWizardModule extends ServletModule { @@ -19,8 +18,7 @@ class MigrationWizardModule extends ServletModule { LOG.info("= Open SCM-Manager in a browser to start the wizard. ="); LOG.info("= ="); LOG.info("=========================================================="); - bind(PushStateDispatcher.class).toInstance((request, response, uri) -> {}); - serve("/images/*", "/styles/*", "/favicon.ico").with(WebResourceServlet.class); + serve("/images/*", "/styles/*", "/favicon.ico").with(StaticResourceServlet.class); serve("/*").with(MigrationWizardServlet.class); } } diff --git a/scm-webapp/src/main/java/sonia/scm/update/MigrationWizardContextListener.java b/scm-webapp/src/main/java/sonia/scm/update/MigrationWizardModuleProvider.java similarity index 51% rename from scm-webapp/src/main/java/sonia/scm/update/MigrationWizardContextListener.java rename to scm-webapp/src/main/java/sonia/scm/update/MigrationWizardModuleProvider.java index 6929c8b10a..ea8f5f7b7d 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/MigrationWizardContextListener.java +++ b/scm-webapp/src/main/java/sonia/scm/update/MigrationWizardModuleProvider.java @@ -1,14 +1,18 @@ package sonia.scm.update; import com.google.inject.Injector; -import com.google.inject.servlet.GuiceServletContextListener; +import com.google.inject.Module; +import sonia.scm.lifecycle.modules.ModuleProvider; 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; - public MigrationWizardContextListener(Injector bootstrapInjector) { + public MigrationWizardModuleProvider(Injector bootstrapInjector) { this.bootstrapInjector = bootstrapInjector; } @@ -17,7 +21,7 @@ public class MigrationWizardContextListener extends GuiceServletContextListener } @Override - protected Injector getInjector() { - return bootstrapInjector.createChildInjector(new MigrationWizardModule()); + public Collection createModules() { + return Collections.singleton(new MigrationWizardModule()); } } diff --git a/scm-webapp/src/main/java/sonia/scm/update/MigrationWizardServlet.java b/scm-webapp/src/main/java/sonia/scm/update/MigrationWizardServlet.java index 479778852b..2386b0dbd7 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/MigrationWizardServlet.java +++ b/scm-webapp/src/main/java/sonia/scm/update/MigrationWizardServlet.java @@ -7,7 +7,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.boot.RestartEvent; +import sonia.scm.lifecycle.RestartEvent; import sonia.scm.event.ScmEventBus; import sonia.scm.update.repository.MigrationStrategy; import sonia.scm.update.repository.MigrationStrategyDao; diff --git a/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java b/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java index b98ecd10ba..8236504543 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java +++ b/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java @@ -11,7 +11,7 @@ import lombok.extern.slf4j.Slf4j; import sonia.scm.NotFoundException; import sonia.scm.SCMContext; import sonia.scm.Stage; -import sonia.scm.boot.RestartEvent; +import sonia.scm.lifecycle.RestartEvent; import sonia.scm.cache.Cache; import sonia.scm.cache.CacheManager; import sonia.scm.filter.WebElement; diff --git a/scm-webapp/src/main/resources/logback.default.xml b/scm-webapp/src/main/resources/logback.default.xml index 8cd5bd450e..bf907b1962 100644 --- a/scm-webapp/src/main/resources/logback.default.xml +++ b/scm-webapp/src/main/resources/logback.default.xml @@ -62,7 +62,7 @@ - + @@ -93,7 +93,7 @@ - + diff --git a/scm-webapp/src/main/webapp/WEB-INF/web.xml b/scm-webapp/src/main/webapp/WEB-INF/web.xml index e69eb9fb2a..f0f8d14612 100644 --- a/scm-webapp/src/main/webapp/WEB-INF/web.xml +++ b/scm-webapp/src/main/webapp/WEB-INF/web.xml @@ -39,26 +39,17 @@ SCM-Manager ${project.version} - + BootstrapFilter - sonia.scm.boot.BootstrapContextFilter + sonia.scm.lifecycle.BootstrapContextFilter BootstrapFilter /* - - - - - - sonia.scm.HttpSessionListenerHolder - diff --git a/scm-webapp/src/test/java/sonia/scm/boot/StaticResourceServletTest.java b/scm-webapp/src/test/java/sonia/scm/StaticResourceServletTest.java similarity index 94% rename from scm-webapp/src/test/java/sonia/scm/boot/StaticResourceServletTest.java rename to scm-webapp/src/test/java/sonia/scm/StaticResourceServletTest.java index 9d7235c97c..8fb61df75d 100644 --- a/scm-webapp/src/test/java/sonia/scm/boot/StaticResourceServletTest.java +++ b/scm-webapp/src/test/java/sonia/scm/StaticResourceServletTest.java @@ -1,4 +1,4 @@ -package sonia.scm.boot; +package sonia.scm; import com.google.common.io.Resources; import org.junit.jupiter.api.Test; @@ -36,7 +36,7 @@ class StaticResourceServletTest { doReturn("/scm").when(request).getContextPath(); doReturn("/scm/resource.txt").when(request).getRequestURI(); doReturn(context).when(request).getServletContext(); - URL resource = Resources.getResource("sonia/scm/boot/resource.txt"); + URL resource = Resources.getResource("sonia/scm/lifecycle/resource.txt"); doReturn(resource).when(context).getResource("/resource.txt"); doReturn(stream).when(response).getOutputStream(); diff --git a/scm-webapp/src/test/java/sonia/scm/boot/InjectionContextRestartStrategyTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/InjectionContextRestartStrategyTest.java similarity index 98% rename from scm-webapp/src/test/java/sonia/scm/boot/InjectionContextRestartStrategyTest.java rename to scm-webapp/src/test/java/sonia/scm/lifecycle/InjectionContextRestartStrategyTest.java index 81e7faa6d5..355dca4a16 100644 --- a/scm-webapp/src/test/java/sonia/scm/boot/InjectionContextRestartStrategyTest.java +++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/InjectionContextRestartStrategyTest.java @@ -1,4 +1,4 @@ -package sonia.scm.boot; +package sonia.scm.lifecycle; import com.github.legman.Subscribe; import org.junit.jupiter.api.BeforeEach; diff --git a/scm-webapp/src/test/java/sonia/scm/boot/RestartServletTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/RestartServletTest.java similarity index 99% rename from scm-webapp/src/test/java/sonia/scm/boot/RestartServletTest.java rename to scm-webapp/src/test/java/sonia/scm/lifecycle/RestartServletTest.java index eac4a12340..c55e0b2b01 100644 --- a/scm-webapp/src/test/java/sonia/scm/boot/RestartServletTest.java +++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/RestartServletTest.java @@ -1,4 +1,4 @@ -package sonia.scm.boot; +package sonia.scm.lifecycle; import com.github.legman.Subscribe; import com.google.common.base.Charsets; diff --git a/scm-webapp/src/test/java/sonia/scm/boot/ServletContextCleanerTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/ServletContextCleanerTest.java similarity index 98% rename from scm-webapp/src/test/java/sonia/scm/boot/ServletContextCleanerTest.java rename to scm-webapp/src/test/java/sonia/scm/lifecycle/ServletContextCleanerTest.java index c9d8c594b4..7e8eff15bb 100644 --- a/scm-webapp/src/test/java/sonia/scm/boot/ServletContextCleanerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/ServletContextCleanerTest.java @@ -1,4 +1,4 @@ -package sonia.scm.boot; +package sonia.scm.lifecycle; import com.google.common.collect.ImmutableSet; import org.junit.Test; diff --git a/scm-webapp/src/test/java/sonia/scm/boot/SetupContextListenerTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/SetupContextListenerTest.java similarity index 99% rename from scm-webapp/src/test/java/sonia/scm/boot/SetupContextListenerTest.java rename to scm-webapp/src/test/java/sonia/scm/lifecycle/SetupContextListenerTest.java index 8912589480..0a37361f9c 100644 --- a/scm-webapp/src/test/java/sonia/scm/boot/SetupContextListenerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/SetupContextListenerTest.java @@ -1,4 +1,4 @@ -package sonia.scm.boot; +package sonia.scm.lifecycle; import com.google.common.collect.Lists; import org.apache.shiro.authc.credential.PasswordService; diff --git a/scm-webapp/src/test/java/sonia/scm/boot/VersionsTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/VersionsTest.java similarity index 98% rename from scm-webapp/src/test/java/sonia/scm/boot/VersionsTest.java rename to scm-webapp/src/test/java/sonia/scm/lifecycle/VersionsTest.java index e5aa8fe3d1..ca42b5894d 100644 --- a/scm-webapp/src/test/java/sonia/scm/boot/VersionsTest.java +++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/VersionsTest.java @@ -1,4 +1,4 @@ -package sonia.scm.boot; +package sonia.scm.lifecycle; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/scm-webapp/src/test/java/sonia/scm/boot/ClassLoaderLifeCycleTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleTest.java similarity index 95% rename from scm-webapp/src/test/java/sonia/scm/boot/ClassLoaderLifeCycleTest.java rename to scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleTest.java index df42b2eac5..0ab98039d1 100644 --- a/scm-webapp/src/test/java/sonia/scm/boot/ClassLoaderLifeCycleTest.java +++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleTest.java @@ -1,4 +1,4 @@ -package sonia.scm.boot; +package sonia.scm.lifecycle.classloading; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -11,7 +11,6 @@ import java.io.Closeable; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; -import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -36,7 +35,7 @@ class ClassLoaderLifeCycleTest { @Test void shouldThrowIllegalStateExceptionAfterShutdown() { ClassLoaderLifeCycle lifeCycle = createMockedLifeCycle(); - lifeCycle.init(); + lifeCycle.initialize(); lifeCycle.shutdown(); assertThrows(IllegalStateException.class, lifeCycle::getBootstrapClassLoader); @@ -45,7 +44,7 @@ class ClassLoaderLifeCycleTest { @Test void shouldCreateBootstrapClassLoaderOnInit() { ClassLoaderLifeCycle lifeCycle = ClassLoaderLifeCycle.create(); - lifeCycle.init(); + lifeCycle.initialize(); assertThat(lifeCycle.getBootstrapClassLoader()).isNotNull(); } @@ -54,7 +53,7 @@ class ClassLoaderLifeCycleTest { void shouldCallTheLeakPreventor() { ClassLoaderLifeCycle lifeCycle = createMockedLifeCycle(); - lifeCycle.init(); + lifeCycle.initialize(); verify(classLoaderLeakPreventor, times(2)).runPreClassLoaderInitiators(); lifeCycle.createChildFirstPluginClassLoader(new URL[0], null, "a"); @@ -72,7 +71,7 @@ class ClassLoaderLifeCycleTest { ClassLoaderLifeCycle lifeCycle = createMockedLifeCycle(webappClassLoader); lifeCycle.setClassLoaderAppendListener(c -> spy(c)); - lifeCycle.init(); + lifeCycle.initialize(); ClassLoader pluginA = lifeCycle.createChildFirstPluginClassLoader(new URL[0], null, "a"); ClassLoader pluginB = lifeCycle.createPluginClassLoader(new URL[0], null, "b"); diff --git a/scm-webapp/src/test/java/sonia/scm/lifecycle/modules/CloseableModuleTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/modules/CloseableModuleTest.java new file mode 100644 index 0000000000..e7df410dc4 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/modules/CloseableModuleTest.java @@ -0,0 +1,32 @@ +package sonia.scm.lifecycle.modules; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import org.junit.jupiter.api.Test; + +import java.io.Closeable; + +import static org.assertj.core.api.Assertions.assertThat; + +class CloseableModuleTest { + + @Test + void shouldCloseCloseables() { + Injector injector = Guice.createInjector(new CloseableModule()); + CloseMe closeMe = injector.getInstance(CloseMe.class); + + injector.getInstance(CloseableModule.class).closeAll(); + assertThat(closeMe.closed).isTrue(); + } + + public static class CloseMe implements Closeable { + + private boolean closed = false; + + @Override + public void close() { + this.closed = true; + } + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/lifecycle/modules/EagerSingletonModuleTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/modules/EagerSingletonModuleTest.java new file mode 100644 index 0000000000..76cd8f1dcb --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/modules/EagerSingletonModuleTest.java @@ -0,0 +1,48 @@ +package sonia.scm.lifecycle.modules; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import org.junit.jupiter.api.Test; +import sonia.scm.EagerSingleton; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import static org.assertj.core.api.Assertions.assertThat; + +class EagerSingletonModuleTest { + + @Test + void shouldInitializeEagerSingletons() { + Injector injector = Guice.createInjector(new EagerSingletonModule(), new EagerTestModule()); + injector.getInstance(EagerSingletonModule.class).initialize(injector); + + Capturer capturer = injector.getInstance(Capturer.class); + assertThat(capturer.value).isEqualTo("eager!"); + } + + public static class EagerTestModule extends AbstractModule { + + @Override + protected void configure() { + bind(Capturer.class); + bind(Eager.class); + } + } + + @Singleton + public static class Capturer { + private String value; + } + + @EagerSingleton + public static class Eager { + + @Inject + public Eager(Capturer capturer) { + capturer.value = "eager!"; + } + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/lifecycle/modules/InjectionLifeCycleTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/modules/InjectionLifeCycleTest.java new file mode 100644 index 0000000000..296af8e235 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/modules/InjectionLifeCycleTest.java @@ -0,0 +1,257 @@ +package sonia.scm.lifecycle.modules; + +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.lifecycle.modules.CloseableModule; +import sonia.scm.Default; +import sonia.scm.EagerSingleton; +import sonia.scm.lifecycle.modules.EagerSingletonModule; +import sonia.scm.lifecycle.modules.InjectionLifeCycle; + +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 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 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; + } + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/lifecycle/modules/MoreMatchersTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/modules/MoreMatchersTest.java new file mode 100644 index 0000000000..baab583461 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/modules/MoreMatchersTest.java @@ -0,0 +1,40 @@ +package sonia.scm.lifecycle.modules; + +import com.google.inject.TypeLiteral; +import com.google.inject.matcher.Matcher; +import org.assertj.core.api.AbstractBooleanAssert; +import org.junit.jupiter.api.Test; + +import javax.inject.Singleton; +import java.io.Serializable; + +import static org.assertj.core.api.Assertions.assertThat; + +class MoreMatchersTest { + + @Test + void shouldMatchSubTypes() { + Matcher matcher = MoreMatchers.isSubtypeOf(Serializable.class); + assertBoolean(matcher, One.class).isTrue(); + assertBoolean(matcher, Two.class).isFalse(); + } + + @Test + void shouldMatchIfAnnotated() { + Matcher matcher = MoreMatchers.isAnnotatedWith(Singleton.class); + assertBoolean(matcher, One.class).isFalse(); + assertBoolean(matcher, Two.class).isTrue(); + } + + private AbstractBooleanAssert assertBoolean(Matcher matcher, Class clazz) { + return assertThat(matcher.matches(TypeLiteral.get(clazz))); + } + + public static class One implements Serializable { + } + + @Singleton + public static class Two { + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/lifecycle/modules/ScmEventBusModuleTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/modules/ScmEventBusModuleTest.java new file mode 100644 index 0000000000..fc6635bb91 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/modules/ScmEventBusModuleTest.java @@ -0,0 +1,35 @@ +package sonia.scm.lifecycle.modules; + +import com.github.legman.Subscribe; +import com.google.inject.Guice; +import com.google.inject.Injector; +import org.junit.jupiter.api.Test; +import sonia.scm.event.LegmanScmEventBus; + +import static org.assertj.core.api.Assertions.assertThat; + +class ScmEventBusModuleTest { + + @Test + void shouldRegisterInstance() { + LegmanScmEventBus eventBus = new LegmanScmEventBus(); + + Injector injector = Guice.createInjector(new ScmEventBusModule(eventBus)); + Listener listener = injector.getInstance(Listener.class); + + eventBus.post("hello"); + + assertThat(listener.message).isEqualTo("hello"); + } + + public static class Listener { + + private String message; + + @Subscribe(async = false) + public void receive(String message) { + this.message = message; + } + + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/lifecycle/modules/ScmInitializerModuleTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/modules/ScmInitializerModuleTest.java new file mode 100644 index 0000000000..8edcd2b6fc --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/modules/ScmInitializerModuleTest.java @@ -0,0 +1,31 @@ +package sonia.scm.lifecycle.modules; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import org.junit.jupiter.api.Test; +import sonia.scm.Initable; +import sonia.scm.SCMContextProvider; + +import static org.assertj.core.api.Assertions.assertThat; + +class ScmInitializerModuleTest { + + @Test + void shouldInitializeInstances() { + Injector injector = Guice.createInjector(new ScmInitializerModule()); + InitializeMe instance = injector.getInstance(InitializeMe.class); + + assertThat(instance.initialized).isTrue(); + } + + public static class InitializeMe implements Initable { + + private boolean initialized = false; + + @Override + public void init(SCMContextProvider context) { + this.initialized = true; + } + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/lifecycle/modules/ServletContextListenerHolderTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/modules/ServletContextListenerHolderTest.java new file mode 100644 index 0000000000..8c78b067fa --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/modules/ServletContextListenerHolderTest.java @@ -0,0 +1,91 @@ +package sonia.scm.lifecycle.modules; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Key; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(MockitoExtension.class) +class ServletContextListenerHolderTest { + + @Test + void shouldInitializeEveryContextListener() { + CountingListener one = new CountingListener(41); + CountingListener two = new CountingListener(41); + + ServletContextListenerHolder holder = createHolder(one, two); + holder.contextInitialized(null); + + assertThat(one.counter).isEqualTo(42); + assertThat(one.counter).isEqualTo(42); + } + + @Test + void shouldDestroyEveryContextListener() { + CountingListener one = new CountingListener(43); + CountingListener two = new CountingListener(43); + + ServletContextListenerHolder holder = createHolder(one, two); + holder.contextDestroyed(null); + + assertThat(one.counter).isEqualTo(42); + assertThat(one.counter).isEqualTo(42); + } + + @Test + void shouldNotFailWithoutServletContextListenerBound(){ + Injector injector = Guice.createInjector(); + ServletContextListenerHolder holder = injector.getInstance(ServletContextListenerHolder.class); + holder.contextInitialized(null); + holder.contextDestroyed(null); + } + + private ServletContextListenerHolder createHolder(CountingListener one, CountingListener two) { + Injector injector = Guice.createInjector(new ListenerModule(one, two)); + return injector.getInstance(ServletContextListenerHolder.class); + } + + public static class ListenerModule extends AbstractModule { + + private final Set listeners; + + ListenerModule(ServletContextListener... listeners) { + this.listeners = new HashSet<>(Arrays.asList(listeners)); + } + + @Override + protected void configure() { + bind(new Key>(){}).toInstance(listeners); + } + } + + public static class CountingListener implements ServletContextListener { + + private int counter; + + CountingListener(int counter) { + this.counter = counter; + } + + @Override + public void contextInitialized(ServletContextEvent sce) { + counter++; + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + counter--; + } + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/lifecycle/modules/ServletContextModuleTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/modules/ServletContextModuleTest.java new file mode 100644 index 0000000000..0684ec6d6d --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/modules/ServletContextModuleTest.java @@ -0,0 +1,64 @@ +package sonia.scm.lifecycle.modules; + +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; + } + + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/boot/SingleViewServletTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/view/SingleViewServletTest.java similarity index 97% rename from scm-webapp/src/test/java/sonia/scm/boot/SingleViewServletTest.java rename to scm-webapp/src/test/java/sonia/scm/lifecycle/view/SingleViewServletTest.java index 9dfebdc571..3bdd83577e 100644 --- a/scm-webapp/src/test/java/sonia/scm/boot/SingleViewServletTest.java +++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/view/SingleViewServletTest.java @@ -1,6 +1,5 @@ -package sonia.scm.boot; +package sonia.scm.lifecycle.view; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; diff --git a/scm-webapp/src/test/java/sonia/scm/boot/SingleViewTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/view/SingleViewTest.java similarity index 68% rename from scm-webapp/src/test/java/sonia/scm/boot/SingleViewTest.java rename to scm-webapp/src/test/java/sonia/scm/lifecycle/view/SingleViewTest.java index f520aea504..d3186f040b 100644 --- a/scm-webapp/src/test/java/sonia/scm/boot/SingleViewTest.java +++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/view/SingleViewTest.java @@ -1,20 +1,19 @@ -package sonia.scm.boot; +package sonia.scm.lifecycle.view; +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.ArgumentCaptor; -import org.mockito.Captor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.StaticResourceServlet; +import sonia.scm.lifecycle.modules.ModuleProvider; import javax.servlet.FilterConfig; import javax.servlet.ServletContext; -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import java.io.IOException; @@ -26,22 +25,19 @@ import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class SingleViewTest { - @Mock - private ServletContext servletContext; - @Mock private HttpServletRequest request; - @Captor - private ArgumentCaptor captor; - private GuiceFilter guiceFilter; @BeforeEach void setUpGuiceFilter() throws ServletException { guiceFilter = new GuiceFilter(); + + ServletContext servletContext = mock(ServletContext.class); FilterConfig config = mock(FilterConfig.class); doReturn(servletContext).when(config).getServletContext(); + guiceFilter.init(config); } @@ -52,10 +48,10 @@ class SingleViewTest { @Test void shouldCreateViewControllerForView() { - ServletContextListener listener = SingleView.view("/my-template", 409); + ModuleProvider moduleProvider = SingleView.view("/my-template", 409); when(request.getContextPath()).thenReturn("/scm"); - ViewController instance = findViewController(listener); + ViewController instance = findViewController(moduleProvider); assertThat(instance.getTemplate()).isEqualTo("/my-template"); View view = instance.createView(request); @@ -64,17 +60,17 @@ class SingleViewTest { @Test void shouldCreateViewControllerForError() { - ServletContextListener listener = SingleView.error(new IOException("awesome io")); + ModuleProvider moduleProvider = SingleView.error(new IOException("awesome io")); when(request.getContextPath()).thenReturn("/scm"); - ViewController instance = findViewController(listener); + ViewController instance = findViewController(moduleProvider); assertErrorViewController(instance, "awesome io"); } @Test void shouldBindServlets() { - ServletContextListener listener = SingleView.error(new IOException("awesome io")); - Injector injector = findInjector(listener); + ModuleProvider moduleProvider = SingleView.error(new IOException("awesome io")); + Injector injector = Guice.createInjector(moduleProvider.createModules()); assertThat(injector.getInstance(StaticResourceServlet.class)).isNotNull(); assertThat(injector.getInstance(SingleViewServlet.class)).isNotNull(); @@ -94,18 +90,9 @@ class SingleViewTest { ); } - private ViewController findViewController(ServletContextListener listener) { - Injector injector = findInjector(listener); + private ViewController findViewController(ModuleProvider moduleProvider) { + Injector injector = Guice.createInjector(moduleProvider.createModules()); return injector.getInstance(ViewController.class); } - private Injector findInjector(ServletContextListener listener) { - listener.contextInitialized(new ServletContextEvent(servletContext)); - - verify(servletContext).setAttribute(anyString(), captor.capture()); - - return captor.getValue(); - } - - } diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/PluginProcessorTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/PluginProcessorTest.java index 5a38cacd3f..8b352b8e68 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/PluginProcessorTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/PluginProcessorTest.java @@ -42,7 +42,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import sonia.scm.boot.ClassLoaderLifeCycle; +import sonia.scm.lifecycle.classloading.ClassLoaderLifeCycle; import static org.hamcrest.Matchers.*; diff --git a/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java b/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java index 468efe3ecd..f177d44e63 100644 --- a/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java +++ b/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java @@ -16,7 +16,7 @@ import org.mockito.MockSettings; import org.mockito.internal.creation.MockSettingsImpl; import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnitRunner; -import sonia.scm.boot.RestartEvent; +import sonia.scm.lifecycle.RestartEvent; import sonia.scm.cache.Cache; import sonia.scm.cache.CacheManager; import sonia.scm.event.ScmEventBus; diff --git a/scm-webapp/src/test/resources/sonia/scm/boot/resource.txt b/scm-webapp/src/test/resources/sonia/scm/lifecycle/resource.txt similarity index 100% rename from scm-webapp/src/test/resources/sonia/scm/boot/resource.txt rename to scm-webapp/src/test/resources/sonia/scm/lifecycle/resource.txt