001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.commons.logging.impl;
019    
020    import java.lang.reflect.InvocationTargetException;
021    import java.lang.reflect.Method;
022    
023    import javax.servlet.ServletContextEvent;
024    import javax.servlet.ServletContextListener;
025    
026    import org.apache.commons.logging.LogFactory;
027    
028    /**
029     * This class is capable of receiving notifications about the undeployment of
030     * a webapp, and responds by ensuring that commons-logging releases all
031     * memory associated with the undeployed webapp.
032     * <p>
033     * In general, the WeakHashtable support added in commons-logging release 1.1
034     * ensures that logging classes do not hold references that prevent an
035     * undeployed webapp's memory from being garbage-collected even when multiple
036     * copies of commons-logging are deployed via multiple classloaders (a
037     * situation that earlier versions had problems with). However there are
038     * some rare cases where the WeakHashtable approach does not work; in these
039     * situations specifying this class as a listener for the web application will
040     * ensure that all references held by commons-logging are fully released.
041     * <p>
042     * To use this class, configure the webapp deployment descriptor to call
043     * this class on webapp undeploy; the contextDestroyed method will tell
044     * every accessible LogFactory class that the entry in its map for the
045     * current webapp's context classloader should be cleared.
046     *
047     * @version $Id: ServletContextCleaner.html 862526 2013-05-20 17:07:42Z tn $
048     * @since 1.1
049     */
050    public class ServletContextCleaner implements ServletContextListener {
051    
052        private static final Class[] RELEASE_SIGNATURE = {ClassLoader.class};
053    
054        /**
055         * Invoked when a webapp is undeployed, this tells the LogFactory
056         * class to release any logging information related to the current
057         * contextClassloader.
058         */
059        public void contextDestroyed(ServletContextEvent sce) {
060            ClassLoader tccl = Thread.currentThread().getContextClassLoader();
061    
062            Object[] params = new Object[1];
063            params[0] = tccl;
064    
065            // Walk up the tree of classloaders, finding all the available
066            // LogFactory classes and releasing any objects associated with
067            // the tccl (ie the webapp).
068            //
069            // When there is only one LogFactory in the classpath, and it
070            // is within the webapp being undeployed then there is no problem;
071            // garbage collection works fine.
072            //
073            // When there are multiple LogFactory classes in the classpath but
074            // parent-first classloading is used everywhere, this loop is really
075            // short. The first instance of LogFactory found will
076            // be the highest in the classpath, and then no more will be found.
077            // This is ok, as with this setup this will be the only LogFactory
078            // holding any data associated with the tccl being released.
079            //
080            // When there are multiple LogFactory classes in the classpath and
081            // child-first classloading is used in any classloader, then multiple
082            // LogFactory instances may hold info about this TCCL; whenever the
083            // webapp makes a call into a class loaded via an ancestor classloader
084            // and that class calls LogFactory the tccl gets registered in
085            // the LogFactory instance that is visible from the ancestor
086            // classloader. However the concrete logging library it points
087            // to is expected to have been loaded via the TCCL, so the
088            // underlying logging lib is only initialised/configured once.
089            // These references from ancestor LogFactory classes down to
090            // TCCL classloaders are held via weak references and so should
091            // be released but there are circumstances where they may not.
092            // Walking up the classloader ancestry ladder releasing
093            // the current tccl at each level tree, though, will definitely
094            // clear any problem references.
095            ClassLoader loader = tccl;
096            while (loader != null) {
097                // Load via the current loader. Note that if the class is not accessible
098                // via this loader, but is accessible via some ancestor then that class
099                // will be returned.
100                try {
101                    Class logFactoryClass = loader.loadClass("org.apache.commons.logging.LogFactory");
102                    Method releaseMethod = logFactoryClass.getMethod("release", RELEASE_SIGNATURE);
103                    releaseMethod.invoke(null, params);
104                    loader = logFactoryClass.getClassLoader().getParent();
105                } catch(ClassNotFoundException ex) {
106                    // Neither the current classloader nor any of its ancestors could find
107                    // the LogFactory class, so we can stop now.
108                    loader = null;
109                } catch(NoSuchMethodException ex) {
110                    // This is not expected; every version of JCL has this method
111                    System.err.println("LogFactory instance found which does not support release method!");
112                    loader = null;
113                } catch(IllegalAccessException ex) {
114                    // This is not expected; every ancestor class should be accessible
115                    System.err.println("LogFactory instance found which is not accessable!");
116                    loader = null;
117                } catch(InvocationTargetException ex) {
118                    // This is not expected
119                    System.err.println("LogFactory instance release method failed!");
120                    loader = null;
121                }
122            }
123    
124            // Just to be sure, invoke release on the LogFactory that is visible from
125            // this ServletContextCleaner class too. This should already have been caught
126            // by the above loop but just in case...
127            LogFactory.release(tccl);
128        }
129    
130        /**
131         * Invoked when a webapp is deployed. Nothing needs to be done here.
132         */
133        public void contextInitialized(ServletContextEvent sce) {
134            // do nothing
135        }
136    }