mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-14 17:26:22 +01:00
simplify scm-manager bootstrap
We have simplified the scm-manager bootstrap process, by replacing the ServletContextListener call hierarchy with much simpler ModuleProviders. We have also removed the parent injector. Now we create always a new injector. If something goes wrong in the process of injector creation, we will show a nicely styled error page instead of stacktrace on a white page.
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
package sonia.scm.boot;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.multibindings.Multibinder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.CloseableModule;
|
||||
import sonia.scm.Default;
|
||||
import sonia.scm.EagerSingleton;
|
||||
import sonia.scm.EagerSingletonModule;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletContextEvent;
|
||||
import javax.servlet.ServletContextListener;
|
||||
import java.io.Closeable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class InjectionLifeCycleTest {
|
||||
|
||||
@Mock
|
||||
private ServletContext servletContext;
|
||||
|
||||
@Test
|
||||
void shouldInitializeEagerSingletons() {
|
||||
Injector injector = initialize(new EagerSingletonModule(), new EagerModule());
|
||||
|
||||
Messenger messenger = injector.getInstance(Messenger.class);
|
||||
assertThat(messenger.receive()).isEqualTo("eager baby!");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotThrowAnExceptionWithoutEagerSingletons() {
|
||||
Injector injector = initialize(new EagerSingletonModule());
|
||||
|
||||
Messenger messenger = injector.getInstance(Messenger.class);
|
||||
assertThat(messenger.receive()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInitializeServletContextListeners() {
|
||||
Injector injector = initialize(new ServletContextListenerModule());
|
||||
|
||||
Messenger messenger = injector.getInstance(Messenger.class);
|
||||
assertThat(messenger.receive()).isEqualTo("+4+2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCallDestroyOnServletContextListeners() {
|
||||
Injector injector = createInjector(servletContext, new ServletContextListenerModule());
|
||||
|
||||
InjectionLifeCycle lifeCycle = new InjectionLifeCycle(injector);
|
||||
lifeCycle.shutdown();
|
||||
|
||||
Messenger messenger = injector.getInstance(Messenger.class);
|
||||
assertThat(messenger.receive()).isEqualTo("-4-2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCloseInstantiatedCloseables() {
|
||||
Injector injector = createInjector(servletContext, new FortyTwoModule(), new CloseableModule());
|
||||
|
||||
injector.getInstance(Two.class);
|
||||
injector.getInstance(Four.class);
|
||||
|
||||
|
||||
InjectionLifeCycle lifeCycle = new InjectionLifeCycle(injector);
|
||||
lifeCycle.shutdown();
|
||||
|
||||
Messenger messenger = injector.getInstance(Messenger.class);
|
||||
assertThat(messenger.receive()).isEqualTo("42");
|
||||
}
|
||||
|
||||
|
||||
private Injector initialize(Module... modules) {
|
||||
Injector injector = createInjector(servletContext, modules);
|
||||
|
||||
InjectionLifeCycle lifeCycle = new InjectionLifeCycle(injector);
|
||||
lifeCycle.initialize();
|
||||
|
||||
return injector;
|
||||
}
|
||||
|
||||
public static class EagerModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(ImEager.class);
|
||||
}
|
||||
}
|
||||
|
||||
@EagerSingleton
|
||||
public static class ImEager {
|
||||
|
||||
@Inject
|
||||
public ImEager(Messenger messenger) {
|
||||
messenger.send("eager baby!");
|
||||
}
|
||||
}
|
||||
|
||||
public static class FortyTwoModule extends AbstractModule {
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(Four.class);
|
||||
bind(Two.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Singleton
|
||||
public static class Four implements Closeable {
|
||||
|
||||
private final Messenger messenger;
|
||||
|
||||
@Inject
|
||||
public Four(Messenger messenger) {
|
||||
this.messenger = messenger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
messenger.append("4");
|
||||
}
|
||||
}
|
||||
|
||||
@Singleton
|
||||
public static class Two implements Closeable {
|
||||
|
||||
private final Messenger messenger;
|
||||
|
||||
@Inject
|
||||
public Two(Messenger messenger) {
|
||||
this.messenger = messenger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
messenger.append("2");
|
||||
}
|
||||
}
|
||||
|
||||
static Injector createInjector(ServletContext context, Module... modules) {
|
||||
List<Module> moduleList = new ArrayList<>();
|
||||
moduleList.add(new ServletContextModule(context));
|
||||
moduleList.addAll(Arrays.asList(modules));
|
||||
|
||||
return Guice.createInjector(moduleList);
|
||||
}
|
||||
|
||||
public static class ServletContextModule extends AbstractModule {
|
||||
|
||||
private final ServletContext servletContext;
|
||||
|
||||
ServletContextModule(ServletContext servletContext) {
|
||||
this.servletContext = servletContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(ServletContext.class).annotatedWith(Default.class).toInstance(servletContext);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ServletContextListenerModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
Multibinder<ServletContextListener> multibinder = Multibinder.newSetBinder(binder(), ServletContextListener.class);
|
||||
multibinder.addBinding().to(AppendingFourServletContextListener.class);
|
||||
multibinder.addBinding().to(AppendingTwoServletContextListener.class);
|
||||
}
|
||||
}
|
||||
|
||||
public static class AppendingFourServletContextListener extends AppendingServletContextListener {
|
||||
|
||||
@Inject
|
||||
public AppendingFourServletContextListener(Messenger messenger) {
|
||||
super(messenger);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSign() {
|
||||
return "4";
|
||||
}
|
||||
}
|
||||
|
||||
public static class AppendingTwoServletContextListener extends AppendingServletContextListener {
|
||||
|
||||
@Inject
|
||||
public AppendingTwoServletContextListener(Messenger messenger) {
|
||||
super(messenger);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSign() {
|
||||
return "2";
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class AppendingServletContextListener implements ServletContextListener {
|
||||
|
||||
private final Messenger messenger;
|
||||
|
||||
@Inject
|
||||
public AppendingServletContextListener(Messenger messenger) {
|
||||
this.messenger = messenger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
send("+");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent sce) {
|
||||
send("-");
|
||||
}
|
||||
|
||||
private void send(String prefix) {
|
||||
messenger.append(prefix + getSign());
|
||||
}
|
||||
|
||||
protected abstract String getSign();
|
||||
}
|
||||
|
||||
@Singleton
|
||||
public static class Messenger {
|
||||
|
||||
private String message;
|
||||
|
||||
void send(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
void append(String messageToAppend) {
|
||||
send(Strings.nullToEmpty(message) + messageToAppend);
|
||||
}
|
||||
|
||||
String receive() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package sonia.scm.boot;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.servlet.GuiceFilter;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.Default;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ServletContextModuleTest {
|
||||
|
||||
@Mock
|
||||
private ServletContext servletContext;
|
||||
|
||||
private GuiceFilter guiceFilter;
|
||||
|
||||
@BeforeEach
|
||||
void setUpEnvironment() throws ServletException {
|
||||
guiceFilter = new GuiceFilter();
|
||||
FilterConfig filterConfig = mock(FilterConfig.class);
|
||||
when(filterConfig.getServletContext()).thenReturn(servletContext);
|
||||
|
||||
guiceFilter.init(filterConfig);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDownEnvironment() {
|
||||
guiceFilter.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBeAbleToInjectServletContext() {
|
||||
Injector injector = Guice.createInjector(new ServletContextModule());
|
||||
WebComponent instance = injector.getInstance(WebComponent.class);
|
||||
assertThat(instance.context).isSameAs(servletContext);
|
||||
}
|
||||
|
||||
|
||||
public static class WebComponent {
|
||||
|
||||
private ServletContext context;
|
||||
|
||||
@Inject
|
||||
public WebComponent(@Default ServletContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package sonia.scm.boot;
|
||||
|
||||
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,17 @@
|
||||
package sonia.scm.boot;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.servlet.GuiceFilter;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
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 +23,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 +46,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 +58,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 +88,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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user