mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-02-04 13:49:13 +01:00
Merged in feature/simplify_bootstrap (pull request #274)
Feature/simplify bootstrap
This commit is contained in:
@@ -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 ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
|
||||
@@ -94,8 +94,6 @@ public final class SCMContext
|
||||
{
|
||||
provider = new BasicContextProvider();
|
||||
}
|
||||
|
||||
provider.init();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
|
||||
|
||||
package sonia.scm.boot;
|
||||
package sonia.scm.lifecycle;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package sonia.scm.boot;
|
||||
package sonia.scm.lifecycle;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.slf4j.Logger;
|
||||
@@ -0,0 +1,9 @@
|
||||
package sonia.scm.lifecycle;
|
||||
|
||||
public interface LifeCycle {
|
||||
|
||||
void initialize();
|
||||
|
||||
void shutdown();
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package sonia.scm.boot;
|
||||
package sonia.scm.lifecycle;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
@@ -1,4 +1,4 @@
|
||||
package sonia.scm.boot;
|
||||
package sonia.scm.lifecycle;
|
||||
|
||||
/**
|
||||
* Strategy for restarting SCM-Manager.
|
||||
@@ -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) {
|
||||
@@ -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;
|
||||
@@ -1,4 +1,4 @@
|
||||
package sonia.scm.boot;
|
||||
package sonia.scm.lifecycle;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.slf4j.Logger;
|
||||
@@ -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
|
||||
@@ -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) {
|
||||
@@ -1,4 +1,4 @@
|
||||
package sonia.scm.boot;
|
||||
package sonia.scm.lifecycle.classloading;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
|
||||
|
||||
package sonia.scm;
|
||||
package sonia.scm.lifecycle.modules;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package sonia.scm.lifecycle.modules;
|
||||
|
||||
import com.google.inject.Module;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface ModuleProvider {
|
||||
|
||||
Collection<Module> createModules();
|
||||
|
||||
}
|
||||
@@ -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()))));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package sonia.scm.boot;
|
||||
package sonia.scm.lifecycle.view;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -1,4 +1,4 @@
|
||||
package sonia.scm.boot;
|
||||
package sonia.scm.lifecycle.view;
|
||||
|
||||
class View {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package sonia.scm.boot;
|
||||
package sonia.scm.lifecycle.view;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 ------------------------------------------------------------
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package sonia.scm.boot;
|
||||
package sonia.scm.lifecycle;
|
||||
|
||||
import com.github.legman.Subscribe;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -1,4 +1,4 @@
|
||||
package sonia.scm.boot;
|
||||
package sonia.scm.lifecycle;
|
||||
|
||||
import com.github.legman.Subscribe;
|
||||
import com.google.common.base.Charsets;
|
||||
@@ -1,4 +1,4 @@
|
||||
package sonia.scm.boot;
|
||||
package sonia.scm.lifecycle;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.junit.Test;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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");
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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!";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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--;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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.*;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user