001: /**
002: *
003: */package clime.messadmin.providers.lifecycle;
004:
005: import java.beans.Introspector;
006: import java.lang.reflect.Method;
007: import java.sql.Driver;
008: import java.sql.DriverManager;
009: import java.sql.SQLException;
010: import java.util.Enumeration;
011:
012: import javax.servlet.ServletContext;
013: import javax.servlet.ServletContextEvent;
014:
015: import clime.messadmin.providers.spi.ApplicationLifeCycleProvider;
016:
017: /**
018: * Takes care of deregistering (some of) the evil Singletons
019: * when the app shuts down, thereby avoiding (well, trying to avoid)
020: * OOM (java.lang.OutOfMemoryError) on hot restart...
021: *
022: * @see <a href="http://opensource2.atlassian.com/confluence/spring/pages/viewpage.action?pageId=2669">Memory leaks where the classloader cannot be garbage collected</a>
023: * @author Cédrik LIME
024: */
025: public class EvilSingletonsUnregisterer implements
026: ApplicationLifeCycleProvider {
027:
028: /**
029: *
030: */
031: public EvilSingletonsUnregisterer() {
032: super ();
033: }
034:
035: /**
036: * {@inheritDoc}
037: */
038: public int getPriority() {
039: return Integer.MAX_VALUE;
040: }
041:
042: /**
043: * {@inheritDoc}
044: */
045: public void contextDestroyed(ServletContext servletContext) {
046: // ThreadLocals - With Great Power, comes Great Responsibility
047: // Can't do anything here. You need to manually set all your ThreadLocals to null yourself (myThreadLocal.remove() or myThreadLocal.set(null))...
048:
049: final ClassLoader this ClassLoader = Thread.currentThread()
050: .getContextClassLoader();//this.getClass().getClassLoader();
051:
052: // java.beans.Introspector - Slightly less Evil singleton
053: // Flushes the cache of classes
054: // Note this is going to clear ALL the classes, regardless of what ClassLoader or application they came from, but it's all that's available.
055: Introspector.flushCaches();
056:
057: // Jakarta Commons IO >= 1.3
058: // org.apache.commons.io.FileCleaner.exitWhenFinished();
059: try {
060: Class fileCleanerClass = this ClassLoader
061: .loadClass("org.apache.commons.io.FileCleaner");//$NON-NLS-1$
062: if (fileCleanerClass != null
063: && fileCleanerClass.getClassLoader() == this ClassLoader) {
064: // If the given classloader is the same as the classloader of FileCleaner, this means that the lib
065: // has been deployed inside the war, so the thread should be stopped and all resources released.
066: // If the classloader is different, then the thread is owned by the container, so don't stop it.
067: Method ewfMethod = fileCleanerClass.getMethod(
068: "exitWhenFinished", null);//$NON-NLS-1$
069: ewfMethod.invoke(null, null);
070: }
071: } catch (Throwable t) {
072: // ignore
073: }
074:
075: // Jakarta Commons Logging <= 1.0.4
076: // org.apache.commons.logging.LogFactory.release(Thread.currentThread().getContextClassLoader());
077: try {
078: ClassLoader loader = this ClassLoader;
079: while (loader != null) {
080: Class logFactoryClass = loader
081: .loadClass("org.apache.commons.logging.LogFactory");//$NON-NLS-1$
082: Method releaseMethod = logFactoryClass.getMethod(
083: "release", new Class[] { ClassLoader.class });//$NON-NLS-1$
084: releaseMethod.invoke(null,
085: new Object[] { this ClassLoader });
086: loader = logFactoryClass.getClassLoader().getParent();
087: }
088: } catch (Throwable t) {
089: // ignore
090: }
091:
092: // LOGBack
093: // new ch.qos.logback.classic.selector.servlet.ContextDetachingSCL().contextDestroyed(ServletContextEvent sce)
094: try {
095: Class cdClass = this ClassLoader
096: .loadClass("ch.qos.logback.classic.selector.servlet.ContextDetachingSCL");//$NON-NLS-1$
097: Object instance = cdClass.newInstance();
098: Method destroyMethod = cdClass
099: .getMethod(
100: "contextDestroyed", new Class[] { ServletContextEvent.class });//$NON-NLS-1$
101: destroyMethod.invoke(instance,
102: new Object[] { new ServletContextEvent(
103: servletContext) });
104: } catch (Throwable t) {
105: // ignore
106: }
107:
108: // Apache Logging Log4J 1.x
109: // org.apache.log4j.LogManager.shutdown();
110: try {
111: Class lmClass = this ClassLoader
112: .loadClass("org.apache.log4j.LogManager");//$NON-NLS-1$
113: Method shutdownMethod = lmClass.getMethod("shutdown", null);//$NON-NLS-1$
114: shutdownMethod.invoke(null, null);
115: } catch (Throwable t) {
116: // ignore
117: }
118:
119: // java.sql.DriverManager - Evil Singleton
120: // Although registering the JDBC driver in your web app is a horrible, horrible thing to do (the container should always manage your connections), some apps do just that.
121: // Unregister JDBC drivers during shutdown: remove any drivers that were loaded by the same classloader that loaded the web app.
122: Enumeration drivers = DriverManager.getDrivers();
123: while (drivers.hasMoreElements()) {
124: Driver o = (Driver) drivers.nextElement();
125: if (o.getClass().getClassLoader() == this ClassLoader) {
126: // Current driver 'o' is being deregistered
127: try {
128: DriverManager.deregisterDriver(o);
129: } catch (SQLException sqle) {
130: //throw new RuntimeException(sqle.getMessage());//new RuntimeException(sqle);
131: System.err
132: .println("Failed to cleanup DriverManager for webapp:");
133: sqle.printStackTrace(System.err);
134: }
135: } else {
136: // Driver 'o' wasn't loaded by this webapp, so no touching it
137: }
138: }
139: }
140:
141: /**
142: * {@inheritDoc}
143: */
144: public void contextInitialized(ServletContext servletContext) {
145: }
146:
147: }
|