-
Notifications
You must be signed in to change notification settings - Fork 139
Add support for Jakarta servlets #419
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one or more | ||
| * contributor license agreements. See the NOTICE file distributed with | ||
| * this work for additional information regarding copyright ownership. | ||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | ||
| * (the "License"); you may not use this file except in compliance with | ||
| * the License. You may obtain a copy of the License at | ||
| * | ||
| * https://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.apache.commons.logging.jakarta; | ||
|
|
||
| import java.lang.reflect.InvocationTargetException; | ||
| import java.lang.reflect.Method; | ||
|
|
||
| import jakarta.servlet.ServletContextEvent; | ||
| import jakarta.servlet.ServletContextListener; | ||
|
|
||
| import org.apache.commons.logging.LogFactory; | ||
|
|
||
| /** | ||
| * This class is capable of receiving notifications about the undeployment of | ||
| * a webapp, and responds by ensuring that commons-logging releases all | ||
| * memory associated with the undeployed webapp. | ||
| * <p> | ||
| * In general, the WeakHashtable support added in commons-logging release 1.1 | ||
| * ensures that logging classes do not hold references that prevent an | ||
| * undeployed webapp's memory from being garbage-collected even when multiple | ||
| * copies of commons-logging are deployed via multiple class loaders (a | ||
| * situation that earlier versions had problems with). However there are | ||
| * some rare cases where the WeakHashtable approach does not work; in these | ||
| * situations specifying this class as a listener for the web application will | ||
| * ensure that all references held by commons-logging are fully released. | ||
| * </p> | ||
| * <p> | ||
| * To use this class, configure the webapp deployment descriptor to call | ||
| * this class on webapp undeploy; the contextDestroyed method will tell | ||
| * every accessible LogFactory class that the entry in its map for the | ||
| * current webapp's context class loader should be cleared. | ||
| * </p> | ||
| * | ||
| * @since 1.4.0 | ||
| */ | ||
| public class ServletContextCleaner implements ServletContextListener { | ||
|
|
||
| private static final Class<?>[] RELEASE_SIGNATURE = { ClassLoader.class }; | ||
|
|
||
| /** | ||
| * Constructs a new instance. | ||
| */ | ||
| public ServletContextCleaner() { | ||
| // empty | ||
| } | ||
|
|
||
| /** | ||
| * Invoked when a webapp is undeployed, this tells the LogFactory | ||
| * class to release any logging information related to the current | ||
| * contextClassloader. | ||
| */ | ||
| @Override | ||
| public void contextDestroyed(final ServletContextEvent sce) { | ||
| final ClassLoader tccl = Thread.currentThread().getContextClassLoader(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess the common code of the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was going to do that initially but stopped myself. I think the simplest solution for now is to keep the Can you test this PR in your use case? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
tested it and worked well |
||
|
|
||
| final Object[] params = new Object[1]; | ||
| params[0] = tccl; | ||
|
|
||
| // Walk up the tree of class loaders, finding all the available | ||
| // LogFactory classes and releasing any objects associated with | ||
| // the tccl (ie the webapp). | ||
| // | ||
| // When there is only one LogFactory in the classpath, and it | ||
| // is within the webapp being undeployed then there is no problem; | ||
| // garbage collection works fine. | ||
| // | ||
| // When there are multiple LogFactory classes in the classpath but | ||
| // parent-first classloading is used everywhere, this loop is really | ||
| // short. The first instance of LogFactory found will | ||
| // be the highest in the classpath, and then no more will be found. | ||
| // This is ok, as with this setup this will be the only LogFactory | ||
| // holding any data associated with the tccl being released. | ||
| // | ||
| // When there are multiple LogFactory classes in the classpath and | ||
| // child-first classloading is used in any class loader, then multiple | ||
| // LogFactory instances may hold info about this TCCL; whenever the | ||
| // webapp makes a call into a class loaded via an ancestor class loader | ||
| // and that class calls LogFactory the tccl gets registered in | ||
| // the LogFactory instance that is visible from the ancestor | ||
| // class loader. However the concrete logging library it points | ||
| // to is expected to have been loaded via the TCCL, so the | ||
| // underlying logging lib is only initialized/configured once. | ||
| // These references from ancestor LogFactory classes down to | ||
| // TCCL class loaders are held via weak references and so should | ||
| // be released but there are circumstances where they may not. | ||
| // Walking up the class loader ancestry ladder releasing | ||
| // the current tccl at each level tree, though, will definitely | ||
| // clear any problem references. | ||
| ClassLoader loader = tccl; | ||
| while (loader != null) { | ||
| // Load via the current loader. Note that if the class is not accessible | ||
| // via this loader, but is accessible via some ancestor then that class | ||
| // will be returned. | ||
| try { | ||
| @SuppressWarnings("unchecked") | ||
| final Class<LogFactory> logFactoryClass = (Class<LogFactory>) loader.loadClass("org.apache.commons.logging.LogFactory"); | ||
| final Method releaseMethod = logFactoryClass.getMethod("release", RELEASE_SIGNATURE); | ||
| releaseMethod.invoke(null, params); | ||
| loader = logFactoryClass.getClassLoader().getParent(); | ||
| } catch (final ClassNotFoundException ex) { | ||
| // Neither the current class loader nor any of its ancestors could find | ||
| // the LogFactory class, so we can stop now. | ||
| loader = null; | ||
| } catch (final NoSuchMethodException ex) { | ||
| // This is not expected; every version of JCL has this method | ||
| System.err.println("LogFactory instance found which does not support release method!"); | ||
| loader = null; | ||
| } catch (final IllegalAccessException ex) { | ||
| // This is not expected; every ancestor class should be accessible | ||
| System.err.println("LogFactory instance found which is not accessible!"); | ||
| loader = null; | ||
| } catch (final InvocationTargetException ex) { | ||
| // This is not expected | ||
| System.err.println("LogFactory instance release method failed!"); | ||
| loader = null; | ||
| } | ||
| } | ||
|
|
||
| // Just to be sure, invoke release on the LogFactory that is visible from | ||
| // this ServletContextCleaner class too. This should already have been caught | ||
| // by the above loop but just in case... | ||
| LogFactory.release(tccl); | ||
| } | ||
|
|
||
| /** | ||
| * Invoked when a webapp is deployed. Nothing needs to be done here. | ||
| */ | ||
| @Override | ||
| public void contextInitialized(final ServletContextEvent sce) { | ||
| // do nothing | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one or more | ||
| * contributor license agreements. See the NOTICE file distributed with | ||
| * this work for additional information regarding copyright ownership. | ||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | ||
| * (the "License"); you may not use this file except in compliance with | ||
| * the License. You may obtain a copy of the License at | ||
| * | ||
| * https://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.apache.commons.logging.servlet; | ||
|
|
||
| import junit.framework.Test; | ||
| import junit.framework.TestCase; | ||
|
|
||
| import org.apache.commons.logging.PathableClassLoader; | ||
| import org.apache.commons.logging.PathableTestSuite; | ||
| import org.apache.commons.logging.jakarta.ServletContextCleaner; | ||
|
|
||
| /** | ||
| * Tests for ServletContextCleaner utility class. | ||
| */ | ||
| public class BasicJakartaServletTestCase extends TestCase { | ||
|
|
||
| /** | ||
| * Return the tests included in this test suite. | ||
| */ | ||
| public static Test suite() throws Exception { | ||
| // LogFactory in parent | ||
| // LogFactory in child (loads test) | ||
| // LogFactory in tccl | ||
| // | ||
| // Having the test loaded via a loader above the tccl emulates the situation | ||
| // where a web.xml file specifies ServletContextCleaner as a listener, and | ||
| // that class is deployed via a shared class loader. | ||
|
|
||
| final PathableClassLoader parent = new PathableClassLoader(null); | ||
| parent.useExplicitLoader("junit.", Test.class.getClassLoader()); | ||
| parent.addLogicalLib("commons-logging"); | ||
| parent.addLogicalLib("jakarta-servlet-api"); | ||
|
|
||
| final PathableClassLoader child = new PathableClassLoader(parent); | ||
| child.setParentFirst(false); | ||
| child.addLogicalLib("commons-logging"); | ||
| child.addLogicalLib("testclasses"); | ||
|
|
||
| final PathableClassLoader tccl = new PathableClassLoader(child); | ||
| tccl.setParentFirst(false); | ||
| tccl.addLogicalLib("commons-logging"); | ||
|
|
||
| final Class<?> testClass = child.loadClass(BasicJakartaServletTestCase.class.getName()); | ||
| return new PathableTestSuite(testClass, tccl); | ||
| } | ||
|
|
||
| /** | ||
| * Test that calling ServletContextCleaner.contextDestroyed doesn't crash. | ||
| * Testing anything else is rather difficult... | ||
| */ | ||
| public void testBasics() { | ||
| final ServletContextCleaner scc = new ServletContextCleaner(); | ||
| scc.contextDestroyed(null); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can it be updated to at least 6.0.0?