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