package sonia.scm.boot; import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventorFactory; import sonia.scm.plugin.ChildFirstPluginClassLoader; import sonia.scm.plugin.DefaultPluginClassLoader; import java.io.Closeable; import java.io.IOException; import java.net.URL; import java.util.ArrayDeque; import java.util.Deque; import java.util.function.UnaryOperator; import static com.google.common.base.Preconditions.checkState; /** * Creates and shutdown SCM-Manager ClassLoaders. */ public final class ClassLoaderLifeCycle { private static final Logger LOG = LoggerFactory.getLogger(ClassLoaderLifeCycle.class); private final Deque classLoaders = new ArrayDeque<>(); private final ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory; private final ClassLoader webappClassLoader; private ClassLoader bootstrapClassLoader; private UnaryOperator classLoaderAppendListener = c -> c; @VisibleForTesting public static ClassLoaderLifeCycle create() { ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory = new ClassLoaderLeakPreventorFactory(); classLoaderLeakPreventorFactory.setLogger(new LoggingAdapter()); return new ClassLoaderLifeCycle(Thread.currentThread().getContextClassLoader(), classLoaderLeakPreventorFactory); } ClassLoaderLifeCycle(ClassLoader webappClassLoader, ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory) { this.classLoaderLeakPreventorFactory = classLoaderLeakPreventorFactory; this.webappClassLoader = initAndAppend(webappClassLoader); } void init() { bootstrapClassLoader = initAndAppend(new BootstrapClassLoader(webappClassLoader)); } @VisibleForTesting void setClassLoaderAppendListener(UnaryOperator classLoaderAppendListener) { this.classLoaderAppendListener = classLoaderAppendListener; } public ClassLoader getBootstrapClassLoader() { checkState(bootstrapClassLoader != null, "%s was not initialized", ClassLoaderLifeCycle.class.getName()); return bootstrapClassLoader; } public ClassLoader createPluginClassLoader(URL[] urls, ClassLoader parent, String plugin) { LOG.debug("create new PluginClassLoader for {}", plugin); DefaultPluginClassLoader pluginClassLoader = new DefaultPluginClassLoader(urls, parent, plugin); return initAndAppend(pluginClassLoader); } public ClassLoader createChildFirstPluginClassLoader(URL[] urls, ClassLoader parent, String plugin) { LOG.debug("create new ChildFirstPluginClassLoader for {}", plugin); ChildFirstPluginClassLoader pluginClassLoader = new ChildFirstPluginClassLoader(urls, parent, plugin); return initAndAppend(pluginClassLoader); } void shutdown() { LOG.info("shutdown classloader infrastructure"); ClassLoaderAndPreventor clap = classLoaders.poll(); while (clap != null) { clap.shutdown(); clap = classLoaders.poll(); } bootstrapClassLoader = null; } private ClassLoader initAndAppend(ClassLoader originalClassLoader) { LOG.debug("init classloader {}", originalClassLoader); ClassLoader classLoader = classLoaderAppendListener.apply(originalClassLoader); ClassLoaderLeakPreventor preventor = classLoaderLeakPreventorFactory.newLeakPreventor(classLoader); preventor.runPreClassLoaderInitiators(); classLoaders.push(new ClassLoaderAndPreventor(classLoader, preventor)); return classLoader; } private class ClassLoaderAndPreventor { private final ClassLoader classLoader; private final ClassLoaderLeakPreventor preventor; private ClassLoaderAndPreventor(ClassLoader classLoader, ClassLoaderLeakPreventor preventor) { this.classLoader = classLoader; this.preventor = preventor; } void shutdown() { LOG.debug("shutdown classloader {}", classLoader); preventor.runCleanUps(); if (classLoader != webappClassLoader) { close(); } } private void close() { if (classLoader instanceof Closeable) { LOG.trace("close classloader {}", classLoader); try { ((Closeable) classLoader).close(); } catch (IOException e) { LOG.warn("failed to close classloader", e); } } } } }