+ * We assume that {@code classLoader} can load this class. + *
+ * @param classLoader The classloader to use. + * @return An implementation of this class. + */ + private static LogFactory newStandardFactory(final ClassLoader classLoader) { + if (isClassAvailable(LOG4J_TO_SLF4J_BRIDGE, classLoader)) { + try { + return (LogFactory) Class.forName(FACTORY_SLF4J, true, classLoader).getConstructor().newInstance(); + } catch (final LinkageError | ReflectiveOperationException ignored) { + } finally { + logDiagnostic( + "[LOOKUP] Log4j API to SLF4J redirection detected. Loading the SLF4J LogFactory implementation '" + FACTORY_SLF4J + "'."); + } + } + try { + return (LogFactory) Class.forName(FACTORY_LOG4J_API, true, classLoader).getConstructor().newInstance(); + } catch (final LinkageError | ReflectiveOperationException ignored) { + } finally { + logDiagnostic("[LOOKUP] Loading the Log4j API LogFactory implementation '" + FACTORY_LOG4J_API + "'."); + } + try { + return (LogFactory) Class.forName(FACTORY_SLF4J, true, classLoader).getConstructor().newInstance(); + } catch (final LinkageError | ReflectiveOperationException ignored) { + } finally { + logDiagnostic("[LOOKUP] Loading the SLF4J LogFactory implementation '" + FACTORY_SLF4J + "'."); + } + try { + return (LogFactory) Class.forName(FACTORY_DEFAULT, true, classLoader).getConstructor().newInstance(); + } catch (final LinkageError | ReflectiveOperationException ignored) { + } finally { + logDiagnostic("[LOOKUP] Loading the legacy LogFactory implementation '" + FACTORY_DEFAULT + "'."); + } + return null; + } + /** * Gets a new instance of the specified {@code LogFactory} implementation class, loaded by the specified class loader. If that fails, try the class loader * used to load this (abstract) LogFactory. diff --git a/src/test/java/org/apache/commons/logging/security/SecurityForbiddenTestCase.java b/src/test/java/org/apache/commons/logging/security/SecurityForbiddenTestCase.java index 5e87db4..aa4005c 100644 --- a/src/test/java/org/apache/commons/logging/security/SecurityForbiddenTestCase.java +++ b/src/test/java/org/apache/commons/logging/security/SecurityForbiddenTestCase.java @@ -22,6 +22,7 @@ import static org.junit.Assert.assertNotEquals; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Hashtable; @@ -93,10 +94,11 @@ public class SecurityForbiddenTestCase extends TestCase { final Class> clazz = classLoader.loadClass(name); return clazz.getConstructor().newInstance(); } catch (final Exception e) { + final Throwable wrapped = e instanceof InvocationTargetException ? ((InvocationTargetException) e).getTargetException() : e; final StringWriter sw = new StringWriter(); final PrintWriter pw = new PrintWriter(sw); - e.printStackTrace(pw); - fail("Unexpected exception:" + e.getMessage() + ":" + sw.toString()); + wrapped.printStackTrace(pw); + fail("Unexpected exception:" + wrapped.getMessage() + ":" + sw); } return null; } diff --git a/src/test/java/org/apache/commons/logging/tccl/logfactory/AdaptersTcclTestCase.java b/src/test/java/org/apache/commons/logging/tccl/logfactory/AdaptersTcclTestCase.java new file mode 100644 index 0000000..2d97056 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/tccl/logfactory/AdaptersTcclTestCase.java @@ -0,0 +1,76 @@ +/* + * 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 + * + * http://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.tccl.logfactory; + +import junit.framework.Test; +import junit.framework.TestCase; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.logging.PathableClassLoader; +import org.apache.commons.logging.PathableTestSuite; +import org.apache.commons.logging.impl.Log4jApiLogFactory; + +/** + * Verifies that if the TCCL contains only `commons-logging-adapters`, it can load a web-application specific + * logging backend. + */ +public class AdaptersTcclTestCase extends TestCase { + + /** + * Return the tests included in this test suite. + */ + public static Test suite() throws Exception { + // The classloader running the test has access to `commons-logging` + final PathableClassLoader classLoader = new PathableClassLoader(null); + classLoader.useExplicitLoader("junit.", Test.class.getClassLoader()); + classLoader.addLogicalLib("commons-logging"); + classLoader.addLogicalLib("testclasses"); + + // The TCCL has access to both `commons-logging-adapters` and `log4j-api` + final PathableClassLoader tcclLoader = new PathableClassLoader(classLoader); + tcclLoader.addLogicalLib("commons-logging-adapters"); + tcclLoader.addLogicalLib("log4j-api"); + tcclLoader.setParentFirst(false); + + final Class> testClass = classLoader.loadClass(AdaptersTcclTestCase.class.getName()); + return new PathableTestSuite(testClass, tcclLoader); + } + + /** + * Sets up instance variables required by this test case. + */ + @Override + public void setUp() throws Exception { + LogFactory.releaseAll(); + } + + /** + * Tear down instance variables required by this test case. + */ + @Override + public void tearDown() { + LogFactory.releaseAll(); + } + + public void testFactoryLoading() { + final LogFactory factory = LogFactory.getFactory(); + // The implementation comes from the TCCL + assertEquals(Thread.currentThread().getContextClassLoader(), factory.getClass().getClassLoader()); + // It uses the `log4j-api` only available in the TCCL + assertEquals(Log4jApiLogFactory.class.getName(), factory.getClass().getName()); + } +} diff --git a/src/test/java/org/apache/commons/logging/tccl/logfactory/SiblingTcclTestCase.java b/src/test/java/org/apache/commons/logging/tccl/logfactory/SiblingTcclTestCase.java new file mode 100644 index 0000000..54e3279 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/tccl/logfactory/SiblingTcclTestCase.java @@ -0,0 +1,72 @@ +/* + * 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 + * + * http://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.tccl.logfactory; + +import junit.framework.Test; +import junit.framework.TestCase; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.logging.PathableClassLoader; +import org.apache.commons.logging.PathableTestSuite; + +/** + * Verifies that if the TCCL is a sibling of the classloader with `commons-logging` + */ +public class SiblingTcclTestCase extends TestCase { + + /** + * Return the tests included in this test suite. + */ + public static Test suite() throws Exception { + // The classloader running the test has access to `commons-logging` + final PathableClassLoader classLoader = new PathableClassLoader(null); + classLoader.useExplicitLoader("junit.", Test.class.getClassLoader()); + classLoader.addLogicalLib("commons-logging"); + classLoader.addLogicalLib("testclasses"); + + // The TCCL has only access to `log4j-api` and `slf4j-api` + // See https://issues.apache.org/jira/browse/LOGGING-192 + final PathableClassLoader tcclLoader = new PathableClassLoader(null); + tcclLoader.addLogicalLib("log4j-api"); + + final Class> testClass = classLoader.loadClass(SiblingTcclTestCase.class.getName()); + return new PathableTestSuite(testClass, tcclLoader); + } + + /** + * Sets up instance variables required by this test case. + */ + @Override + public void setUp() throws Exception { + LogFactory.releaseAll(); + } + + /** + * Tear down instance variables required by this test case. + */ + @Override + public void tearDown() { + LogFactory.releaseAll(); + } + + public void testFactoryLoading() { + // Loading the factory does not fail as in LOGGING-192 + final LogFactory factory = LogFactory.getFactory(); + // The selected implementation comes from this classloader + assertEquals(getClass().getClassLoader(), factory.getClass().getClassLoader()); + } +}