Merged in feature/simplify_bootstrap (pull request #274)

Feature/simplify bootstrap
This commit is contained in:
Eduard Heimbuch
2019-06-26 09:30:00 +00:00
73 changed files with 1647 additions and 1465 deletions

View File

@@ -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 ----------------------------------------------------------
/**

View File

@@ -94,8 +94,6 @@ public final class SCMContext
{
provider = new BasicContextProvider();
}
provider.init();
}
}
}

View File

@@ -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();
}

View File

@@ -29,7 +29,7 @@
package sonia.scm.boot;
package sonia.scm.lifecycle;
//~--- non-JDK imports --------------------------------------------------------

View File

@@ -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
*

View File

@@ -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 <sebastian.sdorra@gmail.com>
* @since 1.42
*/
public class HttpSessionListenerHolder implements HttpSessionListener
{
/** key type of the session listeners */
private static final Key<Set<HttpSessionListener>> KEY =
Key.get(new TypeLiteral<Set<HttpSessionListener>>() {}
);
/** 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<HttpSessionListener> loadListeners(
HttpSessionEvent event)
{
Set<HttpSessionListener> 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<HttpSessionListener> listenerSet;
}

View File

@@ -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<PluginWrapper> plugins;
private Injector injector;
public interface Factory {
ScmContextListener create(ClassLoader parent, Set<PluginWrapper> plugins);
}
@Inject
public ScmContextListener(@Assisted ClassLoader parent, @Assisted Set<PluginWrapper> plugins)
{
this.parent = parent;
this.plugins = plugins;
}
public Set<PluginWrapper> getPlugins() {
return plugins;
}
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
beforeInjectorCreation();
super.contextInitialized(servletContextEvent);
afterInjectorCreation(servletContextEvent);
}
private void beforeInjectorCreation() {
}
private boolean hasStartupErrors() {
return SCMContext.getContext().getStartupError() != null;
}
@Override
protected List<? extends Module> getModules(ServletContext context) {
DefaultPluginLoader pluginLoader = new DefaultPluginLoader(context, parent, plugins);
ClassOverrides overrides = ClassOverrides.findOverrides(pluginLoader.getUberClassLoader());
List<Module> moduleList = Lists.newArrayList();
moduleList.add(new ResteasyModule());
moduleList.add(ShiroWebModule.guiceFilterModule());
moduleList.add(new WebElementModule(pluginLoader));
moduleList.add(new ScmServletModule(context, pluginLoader, overrides));
moduleList.add(
new ScmSecurityModule(context, pluginLoader.getExtensionProcessor())
);
appendModules(pluginLoader.getExtensionProcessor(), moduleList);
moduleList.addAll(overrides.getModules());
if (SCMContext.getContext().getStage() == Stage.DEVELOPMENT){
moduleList.add(new DebugModule());
}
moduleList.add(new MapperModule());
return moduleList;
}
private void appendModules(ExtensionProcessor ep, List<Module> moduleList) {
for (Class<? extends Module> module : ep.byExtensionPoint(Module.class)) {
try {
LOG.info("add module {}", module);
moduleList.add(module.newInstance());
} catch (IllegalAccessException | InstantiationException ex) {
throw Throwables.propagate(ex);
}
}
}
@Override
protected void withInjector(Injector injector) {
this.injector = injector;
}
private void afterInjectorCreation(ServletContextEvent event) {
if (injector != null && !hasStartupErrors()) {
bindEagerSingletons();
initializeServletContextListeners(event);
}
}
private void bindEagerSingletons() {
injector.getInstance(EagerSingletonModule.class).initialize(injector);
}
private void initializeServletContextListeners(ServletContextEvent event) {
injector.getInstance(ServletContextListenerHolder.class).contextInitialized(event);
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent)
{
if (injector != null &&!hasStartupErrors()) {
closeCloseables();
destroyServletContextListeners(servletContextEvent);
}
super.contextDestroyed(servletContextEvent);
}
private void closeCloseables() {
// 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);
}
}

View File

@@ -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 <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter)
{
encounter.register(new InjectionListener<I>()
{
@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<TypeLiteral<?>> isSubtypeOf(final Class<?> supertype)
{
return isSubtypeOf(TypeLiteral.get(supertype));
}
/**
* Method description
*
*
* @param supertype
*
* @return
*/
private Matcher<TypeLiteral<?>> isSubtypeOf(final TypeLiteral<?> supertype)
{
return new AbstractMatcher<TypeLiteral<?>>()
{
@Override
public boolean matches(TypeLiteral<?> type)
{
return typeIsSubtypeOf(type, supertype);
}
};
}
}

View File

@@ -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);
}
}

View File

@@ -1,440 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra All rights reserved.
* <p>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* <p>
* 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.
* <p>
* 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.
* <p>
* 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<PluginWrapper> 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<PluginWrapper> 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<PluginIndexEntry> {
/**
* Method description
*
*
* @return
*/
@Override
public Iterator<PluginIndexEntry> iterator() {
return getPlugins().iterator();
}
//~--- get methods --------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public List<PluginIndexEntry> getPlugins() {
if (plugins == null) {
plugins = ImmutableList.of();
}
return plugins;
}
//~--- fields -------------------------------------------------------------
/** Field description */
@XmlElement(name = "plugins")
private List<PluginIndexEntry> 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));
}
}
}

View File

@@ -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());
}
/**

View File

@@ -0,0 +1,178 @@
/**
* Copyright (c) 2010, Sebastian Sdorra All rights reserved.
* <p>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* <p>
* 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.
* <p>
* 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.
* <p>
* 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<Module> 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<Module> createBootstrapModules(PluginLoader pluginLoader) {
List<Module> modules = new ArrayList<>(createBaseModules());
modules.add(new BootstrapModule(pluginLoader));
return modules;
}
private List<Module> 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();
}
}

View File

@@ -1,4 +1,4 @@
package sonia.scm.boot;
package sonia.scm.lifecycle;
import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger;

View File

@@ -0,0 +1,9 @@
package sonia.scm.lifecycle;
public interface LifeCycle {
void initialize();
void shutdown();
}

View File

@@ -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<PluginWrapper> 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<PluginWrapper> getPlugins() {
return plugins;
}
private PluginLoader createPluginLoader() {
return new DefaultPluginLoader(servletContext, classLoaderLifeCycle.getBootstrapClassLoader(), plugins);
}
private Set<PluginWrapper> 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<PluginIndexEntry> {
@XmlElement(name = "plugins")
private List<PluginIndexEntry> plugins;
@Override
public Iterator<PluginIndexEntry> iterator() {
return getPlugins().iterator();
}
public List<PluginIndexEntry> 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;
}
}
}

View File

@@ -1,4 +1,4 @@
package sonia.scm.boot;
package sonia.scm.lifecycle;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;

View File

@@ -1,4 +1,4 @@
package sonia.scm.boot;
package sonia.scm.lifecycle;
/**
* Strategy for restarting SCM-Manager.

View File

@@ -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) {

View File

@@ -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;

View File

@@ -1,4 +1,4 @@
package sonia.scm.boot;
package sonia.scm.lifecycle;
import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger;

View File

@@ -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

View File

@@ -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) {

View File

@@ -1,4 +1,4 @@
package sonia.scm.boot;
package sonia.scm.lifecycle.classloading;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -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<Module> createModules() {
ClassOverrides overrides = createClassOverrides();
return createModules(overrides);
}
private List<Module> createModules(ClassOverrides overrides) {
List<Module> moduleList = new ArrayList<>();
moduleList.add(new ResteasyModule());
moduleList.add(ShiroWebModule.guiceFilterModule());
moduleList.add(new WebElementModule(pluginLoader));
moduleList.add(new ScmServletModule(pluginLoader, overrides));
moduleList.add(
new ScmSecurityModule(servletContext, pluginLoader.getExtensionProcessor())
);
appendModules(pluginLoader.getExtensionProcessor(), moduleList);
moduleList.addAll(overrides.getModules());
if (SCMContext.getContext().getStage() == Stage.DEVELOPMENT){
moduleList.add(new DebugModule());
}
moduleList.add(new MapperModule());
return moduleList;
}
private ClassOverrides createClassOverrides() {
ClassLoader uberClassLoader = pluginLoader.getUberClassLoader();
return ClassOverrides.findOverrides(uberClassLoader);
}
private void appendModules(ExtensionProcessor ep, List<Module> moduleList) {
for (Class<? extends Module> module : ep.byExtensionPoint(Module.class)) {
try {
LOG.info("add module {}", module);
moduleList.add(module.newInstance());
} catch (IllegalAccessException | InstantiationException ex) {
throw Throwables.propagate(ex);
}
}
}
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -31,7 +31,7 @@
package sonia.scm;
package sonia.scm.lifecycle.modules;
//~--- non-JDK imports --------------------------------------------------------

View File

@@ -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<WeakReference<Closeable>> closeables = new ConcurrentLinkedDeque<>();
@Override
protected void configure() {
bindListener(MoreMatchers.isSubtypeOf(Closeable.class), new TypeListener() {
@Override
public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
encounter.register((InjectionListener<I>) 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<Closeable> 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);
}
}
}

View File

@@ -1,19 +1,19 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* <p>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* <p>
* 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.
* <p>
* 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.
*
* <p>
* 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<Class<?>> 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 <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter)
{
eagerSingletons.add(type.getRawType());
public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
Class<? super I> 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<TypeLiteral<?>> isAnnotatedWith(
final Class<? extends Annotation> annotation)
{
return new AbstractMatcher<TypeLiteral<?>>()
{
@Override
public boolean matches(TypeLiteral<?> type)
{
return type.getRawType().isAnnotationPresent(annotation);
}
};
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final Set<Class<?>> eagerSingletons = Sets.newHashSet();
}

View File

@@ -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 <T> Optional<T> findInstance(Class<T> clazz) {
Binding<T> binding = injector.getExistingBinding(Key.get(clazz));
if (binding != null) {
return Optional.of(binding.getProvider().get());
}
return Optional.empty();
}
}

View File

@@ -0,0 +1,11 @@
package sonia.scm.lifecycle.modules;
import com.google.inject.Module;
import java.util.Collection;
public interface ModuleProvider {
Collection<Module> createModules();
}

View File

@@ -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<TypeLiteral> isAnnotatedWith(final Class<? extends Annotation> annotation) {
return new AbstractMatcher<TypeLiteral>() {
@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<TypeLiteral> isSubtypeOf(final Class supertype) {
return isSubtypeOf(TypeLiteral.get(supertype));
}
private static Matcher<TypeLiteral> isSubtypeOf(final TypeLiteral supertype) {
return new AbstractMatcher<TypeLiteral>() {
@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()))));
}
}

View File

@@ -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);
}
}

View File

@@ -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<String, String> initParams = ImmutableMap.of(ResteasyContextParameters.RESTEASY_SERVLET_MAPPING_PREFIX, "/api");
serve("/api/*").with(HttpServletDispatcher.class, initParams);
serve("/api/*").with(ResteasyAllInOneServletDispatcher.class, initParams);
}
}

View File

@@ -1,19 +1,19 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* <p>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* <p>
* 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.
* <p>
* 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.
*
* <p>
* 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 <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter)
{
encounter.register(new InjectionListener<I>()
{
@Override
public void afterInjection(Object object)
{
ScmEventBus.getInstance().register(object);
}
});
public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
encounter.register((InjectionListener<I>) object -> eventBus.register(object));
}
});
}
}

View File

@@ -0,0 +1,71 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
* <p>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* <p>
* 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.
* <p>
* 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.
* <p>
* 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 <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
encounter.register((InjectionListener<I>) i -> {
LOG.trace("initialize initable {}", i.getClass());
Initable initable = (Initable) i;
initable.init(SCMContext.getContext());
});
}
});
}
}

View File

@@ -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;

View File

@@ -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 <T>
*/
private <T> void bind(Class<T> clazz,
Class<? extends T> defaultImplementation)
{
private <T> void bind(Class<T> clazz, Class<? extends T> defaultImplementation) {
Class<? extends T> 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 <T>
*/
private <T> void bindDecorated(Class<T> clazz,
Class<? extends T> defaultImplementation,
Class<? extends Provider<T>> providerClass)
{
private <T> void bindDecorated(
Class<T> clazz, Class<? extends T> defaultImplementation, Class<? extends Provider<T>> providerClass
) {
Class<? extends T> 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 <T>
*
* @return
*/
private <T> Class<? extends T> find(Class<T> clazz,
Class<? extends T> defaultImplementation)
{
private <T> Class<? extends T> find(Class<T> clazz, Class<? extends T> defaultImplementation) {
Class<? extends T> 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;
}

View File

@@ -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<ServletContextListener> listenerSet;
private Set<ServletContextListener> getListenerSet() {
if (listenerSet == null) {
return Collections.emptySet();
}
return listenerSet;
}
}
private final Set<ServletContextListener> listenerSet;
/**
* Constructs ...
*
*
* @param listenerSet
*/
@Inject
public ServletContextListenerHolder(Set<ServletContextListener> 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<ServletContextListener> listenerSet;
}

View File

@@ -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());
}
}

View File

@@ -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;
}

View File

@@ -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<Module> 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);
}

View File

@@ -1,4 +1,4 @@
package sonia.scm.boot;
package sonia.scm.lifecycle.view;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -1,4 +1,4 @@
package sonia.scm.boot;
package sonia.scm.lifecycle.view;
class View {

View File

@@ -1,4 +1,4 @@
package sonia.scm.boot;
package sonia.scm.lifecycle.view;
import javax.servlet.http.HttpServletRequest;

View File

@@ -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;

View File

@@ -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 ------------------------------------------------------------

View File

@@ -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);
}
}

View File

@@ -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<Module> createModules() {
return Collections.singleton(new MigrationWizardModule());
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -62,7 +62,7 @@
<logger name="sonia.scm.filter.GZipResponseStream" level="WARN" />
<logger name="sonia.scm.util.ServiceUtil" level="WARN" />
<logger name="sonia.scm.ScmEventBusModule" level="DEBUG" />
<logger name="sonia.scm.lifecycle.modules.ScmEventBusModule" level="DEBUG" />
<logger name="sonia.scm.event.LegmanScmEventBus" level="DEBUG" />
<logger name="sonia.scm.plugin.ext.DefaultAnnotationScanner" level="INFO" />
<logger name="sonia.scm.security.ConfigurableLoginAttemptHandler" level="DEBUG" />
@@ -93,7 +93,7 @@
<logger name="org.jboss.resteasy" level="INFO" />
<logger name="sonia.scm.boot.RestartServlet" level="TRACE" />
<logger name="sonia.scm.lifecycle.RestartServlet" level="TRACE" />
<root level="WARN">
<appender-ref ref="STDOUT" />

View File

@@ -39,26 +39,17 @@
<display-name>SCM-Manager ${project.version}</display-name>
<!-- bootstraping -->
<!-- bootstrapping -->
<filter>
<filter-name>BootstrapFilter</filter-name>
<filter-class>sonia.scm.boot.BootstrapContextFilter</filter-class>
<filter-class>sonia.scm.lifecycle.BootstrapContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>BootstrapFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- capture sessions -->
<!--
TODO remove, we need no longer a session
-->
<listener>
<listener-class>sonia.scm.HttpSessionListenerHolder</listener-class>
</listener>
<!-- basic configuration -->

View File

@@ -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();

View File

@@ -1,4 +1,4 @@
package sonia.scm.boot;
package sonia.scm.lifecycle;
import com.github.legman.Subscribe;
import org.junit.jupiter.api.BeforeEach;

View File

@@ -1,4 +1,4 @@
package sonia.scm.boot;
package sonia.scm.lifecycle;
import com.github.legman.Subscribe;
import com.google.common.base.Charsets;

View File

@@ -1,4 +1,4 @@
package sonia.scm.boot;
package sonia.scm.lifecycle;
import com.google.common.collect.ImmutableSet;
import org.junit.Test;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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");

View File

@@ -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;
}
}
}

View File

@@ -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!";
}
}
}

View File

@@ -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<Module> moduleList = new ArrayList<>();
moduleList.add(new ServletContextModule(context));
moduleList.addAll(Arrays.asList(modules));
return Guice.createInjector(moduleList);
}
public static class ServletContextModule extends AbstractModule {
private final ServletContext servletContext;
ServletContextModule(ServletContext servletContext) {
this.servletContext = servletContext;
}
@Override
protected void configure() {
bind(ServletContext.class).annotatedWith(Default.class).toInstance(servletContext);
}
}
public static class ServletContextListenerModule extends AbstractModule {
@Override
protected void configure() {
Multibinder<ServletContextListener> multibinder = Multibinder.newSetBinder(binder(), ServletContextListener.class);
multibinder.addBinding().to(AppendingFourServletContextListener.class);
multibinder.addBinding().to(AppendingTwoServletContextListener.class);
}
}
public static class AppendingFourServletContextListener extends AppendingServletContextListener {
@Inject
public AppendingFourServletContextListener(Messenger messenger) {
super(messenger);
}
@Override
protected String getSign() {
return "4";
}
}
public static class AppendingTwoServletContextListener extends AppendingServletContextListener {
@Inject
public AppendingTwoServletContextListener(Messenger messenger) {
super(messenger);
}
@Override
protected String getSign() {
return "2";
}
}
public static abstract class AppendingServletContextListener implements ServletContextListener {
private final Messenger messenger;
@Inject
public AppendingServletContextListener(Messenger messenger) {
this.messenger = messenger;
}
@Override
public void contextInitialized(ServletContextEvent sce) {
send("+");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
send("-");
}
private void send(String prefix) {
messenger.append(prefix + getSign());
}
protected abstract String getSign();
}
@Singleton
public static class Messenger {
private String message;
void send(String message) {
this.message = message;
}
void append(String messageToAppend) {
send(Strings.nullToEmpty(message) + messageToAppend);
}
String receive() {
return message;
}
}
}

View File

@@ -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<TypeLiteral> matcher = MoreMatchers.isSubtypeOf(Serializable.class);
assertBoolean(matcher, One.class).isTrue();
assertBoolean(matcher, Two.class).isFalse();
}
@Test
void shouldMatchIfAnnotated() {
Matcher<TypeLiteral> matcher = MoreMatchers.isAnnotatedWith(Singleton.class);
assertBoolean(matcher, One.class).isFalse();
assertBoolean(matcher, Two.class).isTrue();
}
private AbstractBooleanAssert<?> assertBoolean(Matcher<TypeLiteral> matcher, Class<?> clazz) {
return assertThat(matcher.matches(TypeLiteral.get(clazz)));
}
public static class One implements Serializable {
}
@Singleton
public static class Two {
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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<ServletContextListener> listeners;
ListenerModule(ServletContextListener... listeners) {
this.listeners = new HashSet<>(Arrays.asList(listeners));
}
@Override
protected void configure() {
bind(new Key<Set<ServletContextListener>>(){}).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--;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;

View File

@@ -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<Injector> 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();
}
}

View File

@@ -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.*;

View File

@@ -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;