1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.logging.jakarta;
19
20 import java.lang.reflect.InvocationTargetException;
21 import java.lang.reflect.Method;
22
23 import jakarta.servlet.ServletContextEvent;
24 import jakarta.servlet.ServletContextListener;
25
26 import org.apache.commons.logging.LogFactory;
27
28 /**
29 * This class is capable of receiving notifications about the undeployment of a webapp, and responds by ensuring that commons-logging releases all memory
30 * associated with the undeployed webapp.
31 * <p>
32 * In general, we ensurs that logging classes do not hold references that prevent an undeployed webapp's memory from being garbage-collected even when multiple
33 * copies of commons-logging are deployed via multiple class loaders (a situation that earlier versions had problems with). However there are some rare cases
34 * where the this approach does not work; in these situations specifying this class as a listener for the web application will ensure that all
35 * references held by commons-logging are fully released.
36 * </p>
37 * <p>
38 * To use this class, configure the webapp deployment descriptor to call this class on webapp undeploy; the contextDestroyed method will tell every accessible
39 * LogFactory class that the entry in its map for the current webapp's context class loader should be cleared.
40 * </p>
41 *
42 * @since 1.4.0
43 */
44 public class ServletContextCleaner implements ServletContextListener {
45
46 private static final Class<?>[] RELEASE_SIGNATURE = { ClassLoader.class };
47
48 /**
49 * Constructs a new instance.
50 */
51 public ServletContextCleaner() {
52 // empty
53 }
54
55 /**
56 * Invoked when a webapp is undeployed, this tells the LogFactory class to release any logging information related to the current contextClassloader.
57 */
58 @Override
59 public void contextDestroyed(final ServletContextEvent sce) {
60 final ClassLoader tccl = Thread.currentThread().getContextClassLoader();
61 final Object[] params = new Object[1];
62 params[0] = tccl;
63 // Walk up the tree of class loaders, finding all the available
64 // LogFactory classes and releasing any objects associated with
65 // the tccl (ie the webapp).
66 //
67 // When there is only one LogFactory in the classpath, and it
68 // is within the webapp being undeployed then there is no problem;
69 // garbage collection works fine.
70 //
71 // When there are multiple LogFactory classes in the classpath but
72 // parent-first classloading is used everywhere, this loop is really
73 // short. The first instance of LogFactory found will
74 // be the highest in the classpath, and then no more will be found.
75 // This is ok, as with this setup this will be the only LogFactory
76 // holding any data associated with the tccl being released.
77 //
78 // When there are multiple LogFactory classes in the classpath and
79 // child-first classloading is used in any class loader, then multiple
80 // LogFactory instances may hold info about this TCCL; whenever the
81 // webapp makes a call into a class loaded via an ancestor class loader
82 // and that class calls LogFactory the tccl gets registered in
83 // the LogFactory instance that is visible from the ancestor
84 // class loader. However the concrete logging library it points
85 // to is expected to have been loaded via the TCCL, so the7
86 // underlying logging lib is only initialized/configured once.
87 // These references from ancestor LogFactory classes down to
88 // TCCL class loaders are held via weak references and so should
89 // be released but there are circumstances where they may not.
90 // Walking up the class loader ancestry ladder releasing
91 // the current tccl at each level tree, though, will definitely
92 // clear any problem references.
93 ClassLoader loader = tccl;
94 while (loader != null) {
95 // Load via the current loader. Note that if the class is not accessible
96 // via this loader, but is accessible via some ancestor then that class
97 // will be returned.
98 try {
99 @SuppressWarnings("unchecked")
100 final Class<LogFactory> logFactoryClass = (Class<LogFactory>) loader.loadClass("org.apache.commons.logging.LogFactory");
101 final Method releaseMethod = logFactoryClass.getMethod("release", RELEASE_SIGNATURE);
102 releaseMethod.invoke(null, params);
103 loader = logFactoryClass.getClassLoader().getParent();
104 } catch (final ClassNotFoundException ex) {
105 // Neither the current class loader nor any of its ancestors could find
106 // the LogFactory class, so we can stop now.
107 loader = null;
108 } catch (final NoSuchMethodException ex) {
109 // This is not expected; every version of JCL has this method
110 System.err.println("LogFactory instance found which does not support release method!");
111 loader = null;
112 } catch (final IllegalAccessException ex) {
113 // This is not expected; every ancestor class should be accessible
114 System.err.println("LogFactory instance found which is not accessible!");
115 loader = null;
116 } catch (final InvocationTargetException ex) {
117 // This is not expected
118 System.err.println("LogFactory instance release method failed!");
119 loader = null;
120 }
121 }
122 // Just to be sure, invoke release on the LogFactory that is visible from
123 // this ServletContextCleaner class too. This should already have been caught
124 // by the above loop but just in case...
125 LogFactory.release(tccl);
126 }
127
128 /**
129 * Invoked when a webapp is deployed. Nothing needs to be done here.
130 */
131 @Override
132 public void contextInitialized(final ServletContextEvent sce) {
133 // do nothing
134 }
135 }