diff --git a/demonstration/README.txt b/demonstration/README.txt new file mode 100644 index 0000000..723bac6 --- /dev/null +++ b/demonstration/README.txt @@ -0,0 +1,30 @@ +# +# Copyright 2005 The Apache Software Foundation. +# +# Licensed 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. +# + +Contained are JCL Proof Of Concept Demonstrations + +The following jars must be added to this directory: + +Log4j.jar +commons-logging.jar +commons-logging-api.jar + +Create build directories by typing 'ant clean' +Build by typing ant. + +Read the overview in the javadocs created under target/docs. + +When the time comes, run the demonstrations by typing 'ant run' \ No newline at end of file diff --git a/demonstration/build.xml b/demonstration/build.xml new file mode 100644 index 0000000..b51ac8d --- /dev/null +++ b/demonstration/build.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/demonstration/src/java/org/apache/commons/logging/proofofconcept/caller/JCLDemonstrator.java b/demonstration/src/java/org/apache/commons/logging/proofofconcept/caller/JCLDemonstrator.java new file mode 100644 index 0000000..4047c96 --- /dev/null +++ b/demonstration/src/java/org/apache/commons/logging/proofofconcept/caller/JCLDemonstrator.java @@ -0,0 +1,69 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.proofofconcept.caller; + +/** + * Tests the behaviour of calls to logging + * and formats the results. + * The actual logging calls are execute by {@link SomeObject}. + */ +public class JCLDemonstrator{ + + /** + * Runs {@link #runJCL()} and {@link #runStatic()} + * + */ + public void run() { + runJCL(); + runStatic(); + } + + /** + * Runs a test that logs to JCL + * and outputs the results to System.out. + */ + public void runJCL() { + try { + SomeObject someObject = new SomeObject(); + System.out.println("--------------------"); + System.out.println("Logging to JCL:"); + someObject.logToJCL(); + System.out.println("JCL Logging OK"); + } catch (Throwable t) { + System.out.println("JCL Logging FAILED: " + t.getClass()); + System.out.println(t.getMessage()); + System.out.println(""); + } + } + + /** + * Runs a test that logs to Log4J via static calls + * and outputs the results to System.out. + */ + public void runStatic() { + try { + SomeObject someObject = new SomeObject(); + System.out.println("--------------------"); + System.out.println("Logging to Static:"); + someObject.logToStaticLog4J(); + System.out.println("Static Logging: OK"); + } catch (Throwable t) { + System.out.println("Static Logging FAILED: " + t.getClass()); + System.out.println(t.getMessage()); + System.out.println(""); + } + } +} diff --git a/demonstration/src/java/org/apache/commons/logging/proofofconcept/caller/SomeObject.java b/demonstration/src/java/org/apache/commons/logging/proofofconcept/caller/SomeObject.java new file mode 100644 index 0000000..ca0ec93 --- /dev/null +++ b/demonstration/src/java/org/apache/commons/logging/proofofconcept/caller/SomeObject.java @@ -0,0 +1,44 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.proofofconcept.caller; + +import org.apache.commons.logging.LogFactory; +import org.apache.commons.logging.proofofconcept.staticlogger.StaticLog4JLogger; + +/** + * This simulates some application or library code + * that uses logging. + * This separation allows tests to be run + * where this class is defined by either the parent + * or the child classloader. + */ +public class SomeObject { + + /** + * Logs a message to Jakarta Commons Logging. + */ + public void logToJCL() { + LogFactory.getLog("a log").info("A message"); + } + + /** + * Logs a message to Log4j via a class + * which makes a static call. + */ + public void logToStaticLog4J() { + StaticLog4JLogger.info("A message"); + } +} diff --git a/demonstration/src/java/org/apache/commons/logging/proofofconcept/caller/package.html b/demonstration/src/java/org/apache/commons/logging/proofofconcept/caller/package.html new file mode 100644 index 0000000..54aa603 --- /dev/null +++ b/demonstration/src/java/org/apache/commons/logging/proofofconcept/caller/package.html @@ -0,0 +1,24 @@ + + +

Invokes logging +and takes the role of calling application code in these demonstrations. +This separation allows a greater variety of defining classloaders for +the calling application code to be simulated. +

+ + diff --git a/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/ChildFirstClassLoader.java b/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/ChildFirstClassLoader.java new file mode 100644 index 0000000..2c32552 --- /dev/null +++ b/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/ChildFirstClassLoader.java @@ -0,0 +1,52 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.proofofconcept.runner; + +import java.net.URL; +import java.net.URLClassLoader; + +/** + * (Rather slack) implementation of a child first classloader. + * Should be fit for the purpose intended (which is demonstration) + * but a more complete and robust implementation should be + * preferred for more general purposes. + */ +public class ChildFirstClassLoader extends URLClassLoader { + + public ChildFirstClassLoader(URL[] urls, ClassLoader parent) { + super(urls, parent); + } + + protected synchronized Class loadClass(String name, boolean resolve) + throws ClassNotFoundException { + + // very basic implementation + Class result = findLoadedClass(name); + if (result == null) { + try { + result = findClass(name); + if (resolve) { + resolveClass(result); + } + } catch (ClassNotFoundException e) { + result = super.loadClass(name, resolve); + } + } + + return result; + } + +} diff --git a/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/ChildFirstRunner.java b/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/ChildFirstRunner.java new file mode 100644 index 0000000..d69ab9d --- /dev/null +++ b/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/ChildFirstRunner.java @@ -0,0 +1,165 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.proofofconcept.runner; + +import java.net.MalformedURLException; + + +/** + * Runs child first demonstrations. + * @see #main(String[]) + */ +public class ChildFirstRunner extends ClassLoaderRunner { + + + public ChildFirstRunner() throws MalformedURLException { + } + + + public void testCase17() { + int parentUrls = JCL_JAR + STATIC_JAR; + int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + run("17", parentUrls, childUrls, false, true); + } + + public void testCase18() { + int parentUrls = JCL_JAR + STATIC_JAR + CALLER_JAR; + int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + run("18", parentUrls, childUrls, false, true); + } + + public void testCase19() { + int parentUrls = JCL_JAR + STATIC_JAR; + int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + run("19", parentUrls, childUrls, true, true); + } + + public void testCase20() { + int parentUrls = JCL_JAR + STATIC_JAR + CALLER_JAR; + int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + run("20", parentUrls, childUrls, true, true); + } + + public void testCase21() { + int parentUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + run("21", parentUrls, childUrls, false, true); + } + + public void testCase22() { + int parentUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + run("22", parentUrls, childUrls, false, true); + } + + public void testCase23() { + int parentUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + run("23", parentUrls, childUrls, true, true); + } + + public void testCase24() { + int parentUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + run("24", parentUrls, childUrls, true, true); + } + + + public void testCase25() { + int parentUrls = API_JAR + STATIC_JAR; + int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + run("25", parentUrls, childUrls, false, true); + } + + public void testCase26() { + int parentUrls = API_JAR + STATIC_JAR + CALLER_JAR; + int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + run("26", parentUrls, childUrls, false, true); + } + + public void testCase27() { + int parentUrls = API_JAR + STATIC_JAR; + int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + run("27", parentUrls, childUrls, true, true); + } + + public void testCase28() { + int parentUrls = API_JAR + STATIC_JAR + CALLER_JAR; + int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + run("28", parentUrls, childUrls, true, true); + } + + public void testCase29() { + int parentUrls = API_JAR + STATIC_JAR + LOG4J_JAR; + int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + run("29", parentUrls, childUrls, false, true); + } + + public void testCase30() { + int parentUrls = API_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + run("30", parentUrls, childUrls, false, true); + } + + public void testCase31() { + int parentUrls = API_JAR + STATIC_JAR + LOG4J_JAR; + int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + run("31", parentUrls, childUrls, true, true); + } + + public void testCase32() { + int parentUrls = API_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + run("32", parentUrls, childUrls, true, true); + } + + /** + * Runs all child first cases. + * @param args + */ + public static void main(String[] args) + { + ChildFirstRunner runner; + try { + runner = new ChildFirstRunner(); + System.out.println(""); + System.out.println(""); + System.out.println("Running Child First Cases..."); + System.out.println(""); + System.out.println(""); + runner.testCase17(); + runner.testCase18(); + runner.testCase19(); + runner.testCase20(); + runner.testCase21(); + runner.testCase22(); + runner.testCase23(); + runner.testCase24(); + runner.testCase25(); + runner.testCase26(); + runner.testCase27(); + runner.testCase28(); + runner.testCase29(); + runner.testCase30(); + runner.testCase31(); + runner.testCase32(); + } catch (MalformedURLException e) { + System.out.println("Cannot find required jars"); + e.printStackTrace(); + } + + } +} diff --git a/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/ClassLoaderRunner.java b/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/ClassLoaderRunner.java new file mode 100644 index 0000000..21a8f94 --- /dev/null +++ b/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/ClassLoaderRunner.java @@ -0,0 +1,219 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.proofofconcept.runner; + +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; + + +/** + * Runs demonstrations with complex classloader hierarchies + * and outputs formatted descriptions of the tests run. + */ +public class ClassLoaderRunner { + + private static final Class[] EMPTY_CLASSES = {}; + private static final Object[] EMPTY_OBJECTS = {}; + private static final URL[] EMPTY_URLS = {}; + + public static final int LOG4J_JAR = 1 << 0; + public static final int STATIC_JAR = 1 << 1; + public static final int JCL_JAR = 1 << 2; + public static final int CALLER_JAR = 1 << 3; + public static final int API_JAR = 1 << 4; + + private final URL log4jUrl; + private final URL staticUrl; + private final URL jclUrl; + private final URL callerUrl; + private final URL apiUrl; + + /** + * Loads URLs. + * @throws MalformedURLException when any of these URLs cannot + * be resolved + */ + public ClassLoaderRunner() throws MalformedURLException { + log4jUrl = new URL("file:log4j.jar"); + staticUrl = new URL("file:static.jar"); + jclUrl = new URL("file:commons-logging.jar"); + callerUrl = new URL("file:caller.jar"); + apiUrl = new URL("file:commons-logging-api.jar"); + } + + /** + * Runs a demonstration. + * @param caseName human name for test (used for output) + * @param parentJars bitwise code for jars to be definable + * by the parent classloader + * @param childJars bitwise code for jars to be definable + * by the child classloader + * @param setContextClassloader true if the context classloader + * should be set to the child classloader, + * false preserves the default + * @param childFirst true if the child classloader + * should delegate only if it cannot define a class, + * false otherwise + */ + public void run(String caseName, int parentJars, int childJars, + boolean setContextClassloader, boolean childFirst) { + + System.out.println(""); + System.out.println("*****************************"); + System.out.println(""); + System.out.println("Running case " + caseName + "..."); + System.out.println(""); + URL[] parentUrls = urlsForJars(parentJars, "Parent Classloader: "); + URL[] childUrls = urlsForJars(childJars, "Child Classloader: "); + System.out.println("Child context classloader: " + setContextClassloader); + System.out.println("Child first: " + childFirst); + System.out.println(""); + run("org.apache.commons.logging.proofofconcept.caller.JCLDemonstrator", + parentUrls, childUrls, setContextClassloader, childFirst); + System.out.println("*****************************"); + } + + /** + * Converts a bitwise jar code into an array of URLs + * containing approapriate URLs. + * @param jars bitwise jar code + * @param humanLoaderName human name for classloader + * @return URL array, not null possibly empty + */ + private URL[] urlsForJars(int jars, String humanLoaderName) { + List urls = new ArrayList();; + if ((LOG4J_JAR & jars) > 0) { + urls.add(log4jUrl); + } + if ((STATIC_JAR & jars) > 0) { + urls.add(staticUrl); + } + if ((JCL_JAR & jars) > 0) { + urls.add(jclUrl); + } + if ((API_JAR & jars) > 0) { + urls.add(apiUrl); + } + if ((CALLER_JAR & jars) > 0) { + urls.add(callerUrl); + } + System.out.println(humanLoaderName + " " + urls); + URL[] results = (URL[]) urls.toArray(EMPTY_URLS); + return results; + } + + /** + * Runs a demonstration. + * @param testName the human name for this test + * @param parentClassloaderUrls the URL's which should + * be definable by the parent classloader, not null + * @param childClassloaderUrls the URL's which should + * be definable by the child classloader, not null + * @param setContextClassloader true if the context + * classloader should be set to the child classloader, + * false if the default context classloader should + * be maintained + * @param childFirst true if the child classloader + * should delegate only when it cannot define the class, + * false otherwise + */ + public void run (String testName, + URL[] parentClassloaderUrls, + URL[] childClassloaderUrls, + boolean setContextClassloader, + boolean childFirst) { + + URLClassLoader parent = new URLClassLoader(parentClassloaderUrls); + URLClassLoader child = null; + if (childFirst) { + child = new ChildFirstClassLoader(childClassloaderUrls, parent); + } else { + child = new URLClassLoader(childClassloaderUrls, parent); + } + + if (setContextClassloader) { + Thread.currentThread().setContextClassLoader(child); + } + + logDefiningLoaders(child, parent); + + try { + + Class callerClass = child.loadClass(testName); + Method runMethod = callerClass.getDeclaredMethod("run", EMPTY_CLASSES); + Object caller = callerClass.newInstance(); + runMethod.invoke(caller, EMPTY_OBJECTS); + + } catch (Exception e) { + System.out.println("Cannot execute test: " + e.getMessage()); + e.printStackTrace(); + } + + } + + /** + * Logs the classloaders which define important classes + * @param child child ClassLoader, not null + * @param parent parent ClassLoader, not null + */ + private void logDefiningLoaders(ClassLoader child, ClassLoader parent) + { + System.out.println(""); + logDefiningLoaders(child, parent, "org.apache.commons.logging.LogFactory", "JCL "); + logDefiningLoaders(child, parent, "org.apache.log4j.Logger", "Log4j "); + logDefiningLoaders(child, parent, "org.apache.commons.logging.proofofconcept.staticlogger.StaticLog4JLogger", "Static Logger"); + logDefiningLoaders(child, parent, "org.apache.commons.logging.proofofconcept.caller.SomeObject", "Caller "); + System.out.println(""); + } + + /** + * Logs the classloader which defines the class with the given name whose + * loading is initiated by the child classloader. + * @param child child ClassLoader, not null + * @param parent parent ClassLoader, not null + * @param className name of the class to be loaded + * @param humanName the human name for the class + */ + private void logDefiningLoaders(ClassLoader child, ClassLoader parent, String className, String humanName) { + try { + Class clazz = child.loadClass(className); + ClassLoader definingLoader = clazz.getClassLoader(); + if (definingLoader == null) + { + System.out.println(humanName + " defined by SYSTEM class loader"); + } + else if (definingLoader.equals(child)) + { + System.out.println(humanName + " defined by CHILD class loader"); + } + else if (definingLoader.equals(parent)) + { + System.out.println(humanName + " defined by PARENT class loader"); + } + else + { + System.out.println(humanName + " defined by OTHER class loader"); + } + } catch (Exception e) { + System.out.println(humanName + " NOT LOADABLE by application classloader"); + } + } + +} diff --git a/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/ParentFirstRunner.java b/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/ParentFirstRunner.java new file mode 100644 index 0000000..5c102ae --- /dev/null +++ b/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/ParentFirstRunner.java @@ -0,0 +1,165 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.proofofconcept.runner; + +import java.net.MalformedURLException; + + +/** + * Runs parent first demonstrations. + * @see #main(String[]) + */ +public class ParentFirstRunner extends ClassLoaderRunner { + + + public ParentFirstRunner() throws MalformedURLException { + } + + public void testCase1() { + int parentUrls = JCL_JAR + STATIC_JAR; + int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + run("1", parentUrls, childUrls, false, false); + } + + public void testCase2() { + int parentUrls = JCL_JAR + STATIC_JAR + CALLER_JAR; + int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + run("2", parentUrls, childUrls, false, false); + } + + public void testCase3() { + int parentUrls = JCL_JAR + STATIC_JAR; + int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + run("3", parentUrls, childUrls, true, false); + } + + public void testCase4() { + int parentUrls = JCL_JAR + STATIC_JAR + CALLER_JAR; + int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + run("4", parentUrls, childUrls, true, false); + } + + public void testCase5() { + int parentUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + run("5", parentUrls, childUrls, false, false); + } + + public void testCase6() { + int parentUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + run("6", parentUrls, childUrls, false, false); + } + + public void testCase7() { + int parentUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + run("7", parentUrls, childUrls, true, false); + } + + public void testCase8() { + int parentUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + run("8", parentUrls, childUrls, true, false); + } + + + public void testCase9() { + int parentUrls = API_JAR + STATIC_JAR; + int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + run("9", parentUrls, childUrls, false, false); + } + + public void testCase10() { + int parentUrls = API_JAR + STATIC_JAR + CALLER_JAR; + int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + run("10", parentUrls, childUrls, false, false); + } + + public void testCase11() { + int parentUrls = API_JAR + STATIC_JAR; + int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + run("11", parentUrls, childUrls, true, false); + } + + public void testCase12() { + int parentUrls = API_JAR + STATIC_JAR + CALLER_JAR; + int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + run("12", parentUrls, childUrls, true, false); + } + + public void testCase13() { + int parentUrls = API_JAR + STATIC_JAR + LOG4J_JAR; + int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + run("13", parentUrls, childUrls, false, false); + } + + public void testCase14() { + int parentUrls = API_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + run("14", parentUrls, childUrls, false, false); + } + + public void testCase15() { + int parentUrls = API_JAR + STATIC_JAR + LOG4J_JAR; + int childUrls = JCL_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + run("15", parentUrls, childUrls, true, false); + } + + public void testCase16() { + int parentUrls = API_JAR + STATIC_JAR + CALLER_JAR + LOG4J_JAR; + int childUrls = JCL_JAR + STATIC_JAR + LOG4J_JAR; + run("16", parentUrls, childUrls, true, false); + } + + /** + * Runs all parent first cases. + * @param args + */ + public static void main(String[] args) + { + ParentFirstRunner runner; + try { + runner = new ParentFirstRunner(); + + System.out.println(""); + System.out.println(""); + System.out.println("Running Parent First Cases..."); + System.out.println(""); + System.out.println(""); + runner.testCase1(); + runner.testCase2(); + runner.testCase3(); + runner.testCase4(); + runner.testCase5(); + runner.testCase6(); + runner.testCase7(); + runner.testCase8(); + runner.testCase9(); + runner.testCase10(); + runner.testCase11(); + runner.testCase12(); + runner.testCase13(); + runner.testCase14(); + runner.testCase15(); + runner.testCase16(); + } catch (MalformedURLException e) { + System.out.println("Cannot find required jars"); + e.printStackTrace(); + } + + } +} diff --git a/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/package.html b/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/package.html new file mode 100644 index 0000000..94723ea --- /dev/null +++ b/demonstration/src/java/org/apache/commons/logging/proofofconcept/runner/package.html @@ -0,0 +1,23 @@ + + +

Runs the demonstrations.

+

+Also contained is utility code that efficiently sets up the +various classloader hierarchies. +

+ diff --git a/demonstration/src/java/org/apache/commons/logging/proofofconcept/staticlogger/StaticLog4JLogger.java b/demonstration/src/java/org/apache/commons/logging/proofofconcept/staticlogger/StaticLog4JLogger.java new file mode 100644 index 0000000..8d8b2e8 --- /dev/null +++ b/demonstration/src/java/org/apache/commons/logging/proofofconcept/staticlogger/StaticLog4JLogger.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.proofofconcept.staticlogger; + +import org.apache.log4j.Logger; + + + +/** + * This simulates a simple, static binding to Log4J. + */ +public class StaticLog4JLogger { + + public static void info(String message) { + // could have got the logger at the start + Logger.getLogger("Whatever").info(message); + } + +} diff --git a/demonstration/src/java/org/apache/commons/logging/proofofconcept/staticlogger/package.html b/demonstration/src/java/org/apache/commons/logging/proofofconcept/staticlogger/package.html new file mode 100644 index 0000000..8e081c8 --- /dev/null +++ b/demonstration/src/java/org/apache/commons/logging/proofofconcept/staticlogger/package.html @@ -0,0 +1,25 @@ + + +

Calls Log4j. Uses a symbolic reference. +

+

+This allows practical demonstrations of the behaviour of code +that calls Log4J statically when placed in similar +classloader circumstances of JCL. +

+ diff --git a/demonstration/src/java/overview.html b/demonstration/src/java/overview.html new file mode 100644 index 0000000..e6b34c2 --- /dev/null +++ b/demonstration/src/java/overview.html @@ -0,0 +1,832 @@ + + + + + Overview Documentation for JCL Proof Of Concept Demonstrations + + +

Demonstrates Jakarta Commons Logging (JCL) concepts.

+

Introduction

+

This document contains analysis of various JCL use cases. It works +both as an educational document and as a specification (of sorts). +It's intended audience are (potential) JCL developers and (potential) +expert users. The code that accompanies +this document demonstrates the cases analysed in the text. +

+

Familiarity with (advanced) class loading +concepts and terminology is assumed. Please digest the +JCL +Technology Guide before starting. +

+

This approach was inspired by a JCL critique written by Ceki +Guici. +

+

These demonstrations focus on discovery in general and discovery +of Log4J in particular. Not only is Log4J the most important target +but these are some of the most difficult and contentious cases. +Lessons learnt from these cases should easily and safely extrapolate +to other cases. +

+

It is important that this document is as accurate as possible both +in facts and analysis. Readers are encouraged to contribute +corrections and improvements. Please either: +

+ +

Overview

+

The basic set up for these demonstrations is intentionally simple. +There are no tricks that might obscure a clear view of the concepts. +The source code used to run these demonstrations is simple and should +be easy to understand. +

+

Each demonstration is initiated by a runner. The runner loads the +other code components into appropriate ClassLoaders and then calls the caller. +

+

The caller is used to simulate a class that needs to log +something. It is isolated from the code that runs the demonstration +in order to allow a greater variety of ClassLoader situations to be simulated. +The calling code is split +into one class that presents the formatted results and another that +actually performs the calls. Calls are made to JCL and to a static +logger. +

+

The static logger simply contains a call directly to Log4J. This +is useful for comparison. +

+

Now would be a good idea to study the javadocs. +

+

Results are printed (with a little formatting) to System.out. The +run target in the ant build scripts runs all cases. It may be +difficult to run these demonstrations from an IDE (a clean system +ClassLoader is required). +

+

Conventional And Unconventional Context ClassLoaders

+

This analysis will start by making a division between conventional +context classloaders and unconventional ones. Conventionally, the +context classloader for a thread executing code in a particular class +should either be the classloader used to load the class, an ancestor +of that classloader or a descendent of it. The conventional context +classloader cases will focus on context classloaders which obey these +rules. +

+

Conventional Classloader Cases

+

The aim of the set up will be isolate the essentials. Only three +classloaders will be considered: +

+ +

This situation is commonly encountered in containers (where the +child classloader is the application classloader). In practical +situations, the hierarchy is likely to be contain more classloaders +but it should be possible either to extrapolate from these simple +cases or reduce the more complex ones to these. +

+

The analysis will proceed by considering two cases separately: +

+ +

In the parent first cases, when the child classloader initiates +loading, it starts by delegating to the parent classloader. Only if +this classloader does not define the class will the child classloader +attempt to define the class. In the child first cases, the child +classloader will begin by attempting to define the class itself. Only +when it cannot define a class will it delegate to it's parents. +Further discussion of each of these cases will be contained in the +appropriate section. +

+

The other variable is the setting of the context classloader. In +these cases, it will be set to either the system classloader (the +default) or the child classloader (the application classloader). +Perhaps the cases for setting to the parent classloader +should be added (but this would be unusual: conventionally the context +classloader should be either unset or set to the application +classloader). Contributions of these extra cases would be welcomed. +

+

The cases will be presented in a matrix. This contains details of +the classloader set ups including which +which classes are definable by which loaders and how the context +classloader is set. It also contains the results of analysis about +the way that both the static logging and JCL (ideally) should behave. +

+

For example +

+ + + + + + + + + + + + + + + + + +
Context ClassLoaderSystem
ParentJCL+Static
Child + JCL+Static
+ Log4J
+ Caller +
Expected ResultJCL->java.util
+ static FAIL +
+

This describes a case where the context classloader is unset (and +so defaults to the system), the static.jar and commons-logging.jar +are definable by the parent classloader, the static.jar, +commons-logging.jar and log4j are definable by the child. The +caller.jar is also definable by the child classloader. The expected +result in this case is that JCL will discover java.util.logging and +that logging statically to Log4j will fail. +

+

For purposes of reference, an indication is given in those cases +that correspond to examples used by Ceki in his critique. +

+

Analytical notes explaining how these results were obtained are +included below each table. As the cases proceed, the notes will grow +more terse. +

+

Parent First ClassLoader Cases

+
Log4J Defined By Child, JCL By Parent
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Case1234
Ceki Example-12A3
Context ClassLoaderSystemSystemChildChild
ParentJCL+StaticJCL+Static CallerJCL+StaticJCL+Static Caller
Child + JCL+Static
+ Log4J
+ Caller
+ JCL+Static
+ Log4J +
+ JCL+Static
+ Log4J
+ Caller +
+ JCL+Static
+ Log4J +
Expected Result + JCL->java.util
+ static FAIL +
+ JCL->java.util
+ static FAIL +
+ JCL->java.util
+ static FAIL +
+ JCL->java.util
+ static FAIL +
+

One important point to bear in mind when analysing parent-first +classloaders is that the presence or absence in the child classloader +of classes definable by the parent classloader produces no difference +in behaviour: the parent classloader will always load the class in +question. +

+

In the cases above, Log4J is defined (only) by the child and all +JCL classes by the parent classloader. The symbolic references from +Log4JLogger to Log4J classes therefore cannot be resolved as Log4j is +not definable by the parent classloader. For the same reason, the +static call should also always fail. +

+

The context classloader can be of no use (whether set or not) +since Log4JLogger will always be defined by the parent classloader +whether loading is initiated by the child or by the parent loader. +

+

Therefore, in these cases, the appropriate behaviour is for JCL to +discover that java.util.logging is the only available logging system. +

+
Log4J Defined By Parent, JCL By Parent
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Case5678
Ceki Example----
Context ClassLoaderSystemSystemChildChild
ParentLog4J
+ JCL+Static +
Log4J
+ JCL+Static
+ Caller +
Log4J
+ JCL+Static +
Log4J
+ JCL+Static
+ Caller +
ChildJCL+Static
+ Log4J
+ Caller
+
JCL+Static
+ Log4J
+
JCL+Static
+ Log4J
+ Caller +
JCL+Static
+ Log4J +
Expected Result + JCL->log4j
+ static OK +
JCL->log4j
+ static OK +
+ JCL->log4j
+ static OK +
+ JCL->log4j
+ static OK +
+

This demonstrates the usual way to fix the problems encountered +when Log4J is not definable by the loader that defines JCL: just add +the Log4j.jar into the classloader that defines JCL. +

+

This is a very simple set of cases with JCL and static being able +to resolve the appropriate symbolic references to Log4j directly. +Whether the context classloader is set or not in these cases should +make no difference. +

+
Log4J Defined By Child, JCL API By Parent, JCL By Child
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Case9101112
Ceki Example----
Context ClassLoaderSystemSystemChildChild
ParentAPI+StaticAPI+Static
+ Caller +
API+Static + API+Static
+ Caller +
ChildJCL+Static
+ Log4J
+ Caller +
JCL+Static
+ Log4J +
JCL+Static
+ Log4J
+ Caller +
JCL+Static
+ Log4J +
Expected ResultJCL->java.util
+ static FAIL +
JCL->java.util
+ static FAIL +
JCL->log4j
+ static FAIL +
JCL->log4j
+ static FAIL

+
+

These demonstrate variations on the first series of cases where +Log4J is defined by the child. The placement of the static jar does +not vary (from the previous set) and again is expected to +consistently fail. +

+

This time the API jar is placed in the parent classloader and the +full JCL jar in the child. This means that the symbolic reference +from the Log4JLogger class (contained in the full jar but not the +API) to Log4J classes can be resolved. +

+

In these cases, whether JCL can succeed depends on the context +classloader. The delegation model employed by Java ClassLoaders +allows child classloaders to know about parents but not vice versa. +Therefore, the context classloader is the only means available to +attempt to load Log4JLogger. When the context classloader is set to +the child, this classloader defines Log4JLogger and Log4J. Therefore, +JCL should be able to discover Log4J (only) when the context +classloader is set to the child. +

+
Log4J Defined By Parent, JCL API By Parent, JCL By Child
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Case13141516
Ceki Example----
Context ClassLoaderSystemSystemChildChild
ParentLog4J
+ API+Static +
Log4J
+ API+Static
+ Caller +
Log4J
+ API+Static +
Log4J
+ API+Static
+ Caller +
ChildJCL+Static
+ Log4J
+ Caller
JCL+Static
+ Log4J
JCL+Static
+ Log4J
+ Caller +
JCL+Static
+ Log4J
Expected ResultJCL->log4j
+ static OK
JCL->log4j
+ static OK +
JCL->log4j
+ static OK +
JCL->log4j
+ static OK +
+

Trivial variations on the second series. Now API and JCL are +definable only by parent and child respectively (as in the last +series). +

+

Child First ClassLoader Cases

+

The same classloader configuration can be repeated with (this +time) child-first classloading. +

+
Log4J Defined By Child, JCL By Parent
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Case17181920
Ceki Example--56
Context ClassLoaderSystemSystemChildChild
ParentJCL+StaticJCL+Static
+ Caller
JCL+StaticJCL+Static
+ Caller +
ChildJCL+Static
+ Log4J
+ Caller +
JCL+Static
+ Log4J +
JCL+Static
+ Log4J
+ Caller +
JCL+Static
+ Log4J +
Expected ResultJCL->Log4j
+ static OK +
JCL->java.util
+ static FAIL +
JCL->Log4j
+ static OK +
JCL->java.util
+ static FAIL +
+

In child-first cases, the classloader which defines the caller +plays a more important role. When the caller is defined by the child +classloader, both static and JCL should succeed whether the context +classloader is set or not. +

+

When the caller is defined by the parent classloader, this means +that the parent classloader will define the JCL and static classes +(rather than the child). Log4J is not defined by the parent loader +and so the static call must fail in both cases. +

+

With a friendly context classloader, JCL can load an instance of +Log4JLogger whose symbolic references to Log4J can be resolved. +However, the child classloader which defines this class will resolve +the symbolic reference to Log to the class defined by the child +classloader rather than the Log class defined by the parent +classloader. They are not mutually accessible and so JCL must +discover java.util.logging. +

+
Log4J Defined By Parent, JCL By Parent
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Case + 21222324
Ceki Example----
Context ClassLoaderSystemSystemChildChild
ParentLog4J
+ JCL+Static +
Log4J
+ JCL+Static
+ Caller +
Log4J
+ JCL+Static +
Log4J
+ JCL+Static
+ Caller +
ChildJCL+Static
+ Log4J
+ Caller +
JCL+Static
+ Log4J +
JCL+Static
+ Log4J
+ Caller +
JCL+Static
+ Log4J +
Expected ResultJCL->log4j
+ static OK +
JCL->log4j
+ static OK +
JCL->log4j
+ static OK +
JCL->log4j
+ static OK +
+

Trivial case where jar's are definable by both loaders. +

+
Log4J Defined By Child, JCL API By Parent, JCL By Child
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Case25262728
Ceki Example----
Context ClassLoaderSystemSystemChildChild
ParentAPI+StaticAPI+Static
+ Caller +
API+StaticAPI+Static
+ Caller +
ChildJCL+Static
+ Log4J
+ Caller +
JCL+Static
+ Log4J +
JCL+Static
+ Log4J
+ Caller +
JCL+Static
+ Log4J +
Expected ResultJCL->log4j
+ static OK +
JCL->java.util
+ static FAIL +
JCL->log4j
+ static OK +
JCL->java.util
+ static FAIL +
+

(As above) to succeed, the static call requires that Log4J is +definable by the loader which defines the callers. JCL should +discover Log4J in the cases where the static call succeeds. +

+

Even with a friendly context classloader, the Log class referenced +by the Log4JLogger defined by the child classloader will be +inaccessible to the caller (loader by the parent classloader). +

+
Log4J Defined By Parent, JCL API By Parent, JCL By Child
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Case29303132
Ceki Example----
Context ClassLoaderSystemSystemChildChild
ParentLog4J
+ API+Static +
Log4J
+ API+Static
+ Caller +
Log4J
+ API+Static +
Log4J
+ API+Static
+ Caller +
ChildJCL+Static
+ Log4J
+ Caller +
JCL+Static
+ Log4J +
JCL+Static
+ Log4J
+ Caller +
JCL+Static
+ Log4J +
Expected ResultJCL->log4j
+ static OK +
JCL->java.util
+ static OK +
JCL->log4j
+ static OK +
JCL->java.util
+ static OK +
+

These results at first glance may seem a little confusing. The +issue for JCL is that classes needed to log to Log4J are only +definable by the child classloader. +

+

Even with a friendly context classloader, JCL runs into the same +difficulties with accessibility that +occurred above. +

+ +