From bacdf4d7113fdc933e0759f937dd1495c21c35f7 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 21 Jun 2019 08:41:26 +0200 Subject: [PATCH] close every registered Closeable, instead of explicitly close a special set of instances --- .../main/java/sonia/scm/CloseableModule.java | 56 +++++++++++++++++ .../src/main/java/sonia/scm/MoreMatchers.java | 60 +++++++++++++++++++ .../java/sonia/scm/ScmContextListener.java | 15 +---- .../scm/boot/BootstrapContextListener.java | 5 +- .../java/sonia/scm/CloseableModuleTest.java | 32 ++++++++++ .../test/java/sonia/scm/MoreMatchersTest.java | 40 +++++++++++++ 6 files changed, 193 insertions(+), 15 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/CloseableModule.java create mode 100644 scm-webapp/src/main/java/sonia/scm/MoreMatchers.java create mode 100644 scm-webapp/src/test/java/sonia/scm/CloseableModuleTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/MoreMatchersTest.java diff --git a/scm-webapp/src/main/java/sonia/scm/CloseableModule.java b/scm-webapp/src/main/java/sonia/scm/CloseableModule.java new file mode 100644 index 0000000000..9c4054a24a --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/CloseableModule.java @@ -0,0 +1,56 @@ +package sonia.scm; + +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; + +public final class CloseableModule extends AbstractModule { + + private static final Logger LOG = LoggerFactory.getLogger(CloseableModule.class); + + private final Deque> closeables = new ConcurrentLinkedDeque<>(); + + @Override + protected void configure() { + bindListener(MoreMatchers.isSubtypeOf(Closeable.class), new TypeListener() { + @Override + public void hear(TypeLiteral type, TypeEncounter encounter) { + encounter.register((InjectionListener) instance -> { + LOG.debug("register closable {}", instance.getClass()); + Closeable closeable = (Closeable) instance; + closeables.push(new WeakReference<>(closeable)); + }); + } + }); + + bind(CloseableModule.class).toInstance(this); + } + + public void closeAll() { + LOG.debug("close all registered closeables"); + WeakReference reference = closeables.poll(); + while (reference != null) { + Closeable closeable = reference.get(); + close(closeable); + reference = closeables.poll(); + } + } + + private void close(Closeable closeable) { + if (closeable != null) { + LOG.trace("close closeable instance of {}", closeable); + IOUtil.close(closeable); + } + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/MoreMatchers.java b/scm-webapp/src/main/java/sonia/scm/MoreMatchers.java new file mode 100644 index 0000000000..40fe6ee476 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/MoreMatchers.java @@ -0,0 +1,60 @@ +package sonia.scm; + +import com.google.inject.TypeLiteral; +import com.google.inject.matcher.AbstractMatcher; +import com.google.inject.matcher.Matcher; + +import java.lang.annotation.Annotation; + +/** + * Helper methods for Guice matchers, which are not already provided in {@link com.google.inject.matcher.Matchers}. + */ +@SuppressWarnings("unchecked") +final class MoreMatchers { + + private MoreMatchers() {} + + /** + * Returns a matcher which matches TypeListerals which are annotated with the given annotation. + * + * @param annotation annotation to match + * + * @return annotation matcher + */ + static Matcher isAnnotatedWith(final Class annotation) { + return new AbstractMatcher() { + @Override + public boolean matches(TypeLiteral type) { + return type.getRawType().isAnnotationPresent(annotation); + } + }; + } + + /** + * Returns a matcher which maches TypeLiterals which are sub types of the given class. + * + * @param supertype sub type to match + * + * @return sub type matcher + */ + static Matcher isSubtypeOf(final Class supertype) { + return isSubtypeOf(TypeLiteral.get(supertype)); + } + + private static Matcher isSubtypeOf(final TypeLiteral supertype) { + return new AbstractMatcher() { + @Override + public boolean matches(TypeLiteral type) { + return typeIsSubtypeOf(type, supertype); + } + }; + } + + private static boolean typeIsSubtypeOf(TypeLiteral subtype, TypeLiteral supertype) { + // First check that raw types are compatible + // Then check that generic types are compatible! HOW???? + return (subtype.equals(supertype) + || (supertype.getRawType().isAssignableFrom(subtype.getRawType()) + && supertype.equals(subtype.getSupertype(supertype.getRawType())))); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java b/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java index cb57e88933..92073a9e85 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java @@ -174,20 +174,7 @@ public class ScmContextListener extends GuiceResteasyBootstrapServletContextList } 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)); + injector.getInstance(CloseableModule.class).closeAll(); } private void destroyServletContextListeners(ServletContextEvent event) { diff --git a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextListener.java b/scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextListener.java index da67e30f81..68ec18e570 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextListener.java +++ b/scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextListener.java @@ -34,6 +34,7 @@ import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Module; import com.google.inject.assistedinject.FactoryModuleBuilder; +import sonia.scm.CloseableModule; import sonia.scm.EagerSingletonModule; import sonia.scm.SCMContext; import sonia.scm.ScmContextListener; @@ -123,6 +124,7 @@ public class BootstrapContextListener implements ServletContextListener { BootstrapModule bootstrapModule = new BootstrapModule(pluginLoader); ScmInitializerModule scmInitializerModule = new ScmInitializerModule(); EagerSingletonModule eagerSingletonModule = new EagerSingletonModule(); + CloseableModule closeableModule = new CloseableModule(); ScmEventBusModule scmEventBusModule = new ScmEventBusModule(); return Guice.createInjector( @@ -130,7 +132,8 @@ public class BootstrapContextListener implements ServletContextListener { scmContextListenerModule, scmEventBusModule, scmInitializerModule, - eagerSingletonModule + eagerSingletonModule, + closeableModule ); } diff --git a/scm-webapp/src/test/java/sonia/scm/CloseableModuleTest.java b/scm-webapp/src/test/java/sonia/scm/CloseableModuleTest.java new file mode 100644 index 0000000000..682eaa45ba --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/CloseableModuleTest.java @@ -0,0 +1,32 @@ +package sonia.scm; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import org.junit.jupiter.api.Test; + +import java.io.Closeable; + +import static org.assertj.core.api.Assertions.assertThat; + +class CloseableModuleTest { + + @Test + void shouldCloseCloseables() { + Injector injector = Guice.createInjector(new CloseableModule()); + CloseMe closeMe = injector.getInstance(CloseMe.class); + + injector.getInstance(CloseableModule.class).closeAll(); + assertThat(closeMe.closed).isTrue(); + } + + public static class CloseMe implements Closeable { + + private boolean closed = false; + + @Override + public void close() { + this.closed = true; + } + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/MoreMatchersTest.java b/scm-webapp/src/test/java/sonia/scm/MoreMatchersTest.java new file mode 100644 index 0000000000..67c2e59a4e --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/MoreMatchersTest.java @@ -0,0 +1,40 @@ +package sonia.scm; + +import com.google.inject.TypeLiteral; +import com.google.inject.matcher.Matcher; +import org.assertj.core.api.AbstractBooleanAssert; +import org.junit.jupiter.api.Test; + +import javax.inject.Singleton; +import java.io.Serializable; + +import static org.assertj.core.api.Assertions.assertThat; + +class MoreMatchersTest { + + @Test + void shouldMatchSubTypes() { + Matcher matcher = MoreMatchers.isSubtypeOf(Serializable.class); + assertBoolean(matcher, One.class).isTrue(); + assertBoolean(matcher, Two.class).isFalse(); + } + + @Test + void shouldMatchIfAnnotated() { + Matcher matcher = MoreMatchers.isAnnotatedWith(Singleton.class); + assertBoolean(matcher, One.class).isFalse(); + assertBoolean(matcher, Two.class).isTrue(); + } + + private AbstractBooleanAssert assertBoolean(Matcher matcher, Class clazz) { + return assertThat(matcher.matches(TypeLiteral.get(clazz))); + } + + public static class One implements Serializable { + } + + @Singleton + public static class Two { + } + +}