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